aboutsummaryrefslogtreecommitdiff
path: root/src/client/react
diff options
context:
space:
mode:
authorNoah Loomans <noahloomans@gmail.com>2018-01-17 16:26:04 +0100
committerNoah Loomans <noahloomans@gmail.com>2018-01-17 16:26:04 +0100
commit1b3f4ea79f947558573fbce5a2e2d0c2c5dd6a8d (patch)
tree52a41cbd31a69de31edf83578e3055d076fa1c3e /src/client/react
parentc0aa588bc8f85b13b5a55ccd6cdf11bf99048a1c (diff)
Add view code
Diffstat (limited to 'src/client/react')
-rw-r--r--src/client/react/actions/view.js28
-rw-r--r--src/client/react/components/container/Search.js21
-rw-r--r--src/client/react/components/container/View.js70
-rw-r--r--src/client/react/components/page/User.js2
-rw-r--r--src/client/react/index.js2
-rw-r--r--src/client/react/reducers.js2
-rw-r--r--src/client/react/reducers/view.js38
7 files changed, 159 insertions, 4 deletions
diff --git a/src/client/react/actions/view.js b/src/client/react/actions/view.js
new file mode 100644
index 0000000..f9f0be2
--- /dev/null
+++ b/src/client/react/actions/view.js
@@ -0,0 +1,28 @@
+// eslint-disable-next-line import/prefer-default-export
+export const fetchSchedule = user => (dispatch) => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_REQUEST',
+ user,
+ });
+
+ fetch(`/get/${user}`).then(
+ // success
+ (r) => {
+ r.text().then((htmlStr) => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_SUCCESS',
+ user,
+ htmlStr,
+ });
+ });
+ },
+
+ // error
+ () => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_FAILURE',
+ user,
+ });
+ },
+ );
+};
diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js
index 27b0563..9a99833 100644
--- a/src/client/react/components/container/Search.js
+++ b/src/client/react/components/container/Search.js
@@ -29,6 +29,12 @@ class Search extends React.Component {
this.props.dispatch(setUser(this.props.urlUser));
}
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.urlUser !== this.props.urlUser) {
+ this.props.dispatch(setUser(nextProps.urlUser));
+ }
+ }
+
onFocus() {
this.setState({
hasFocus: true,
@@ -51,10 +57,18 @@ class Search extends React.Component {
case 'ArrowDown':
this.props.dispatch(changeSelectedResult(+1));
break;
- case 'Enter':
- if (this.props.selectedResult) {
- this.props.history.push(`/${this.props.selectedResult}`);
+ case 'Enter': {
+ const result = this.props.selectedResult || this.props.results[0];
+
+ if (result === this.props.urlUser) {
+ // 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(this.props.urlUser));
+ } else if (result) {
+ this.props.history.push(`/${result}`);
}
+ }
break;
default:
throw new Error('This should never happen... pls?');
@@ -100,6 +114,7 @@ class Search extends React.Component {
}
Search.propTypes = {
+ results: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedResult: PropTypes.string,
urlUser: PropTypes.string,
isExactMatch: PropTypes.bool.isRequired,
diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js
new file mode 100644
index 0000000..9bac66f
--- /dev/null
+++ b/src/client/react/components/container/View.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import createDOMPurify from 'dompurify';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import { fetchSchedule } from '../../actions/view';
+
+function cleanMeetingpointHTML(htmlStr) {
+ const DOMPurify = createDOMPurify(window);
+
+ return DOMPurify.sanitize(htmlStr, {
+ ADD_ATTR: ['rules'],
+ });
+}
+
+class View extends React.Component {
+ componentDidMount() {
+ if (!this.loadingFinished(this.props.user)) {
+ this.props.dispatch(fetchSchedule(this.props.user));
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.user !== this.props.user && !this.loadingFinished(nextProps.user)) {
+ this.props.dispatch(fetchSchedule(nextProps.user));
+ }
+ }
+
+ loadingFinished(user) {
+ return this.props.schedules.hasOwnProperty(user) &&
+ this.props.schedules[user].state === 'finished';
+ }
+
+ render() {
+ if (!this.loadingFinished(this.props.user)) {
+ return (
+ <div>
+ Loading...
+ </div>
+ );
+ }
+
+ const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user].htmlStr);
+
+ return (
+ // eslint-disable-next-line react/no-danger
+ <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
+ );
+ }
+}
+
+View.propTypes = {
+ user: PropTypes.string,
+ dispatch: PropTypes.func.isRequired,
+ schedules: PropTypes.objectOf(PropTypes.shape({
+ state: PropTypes.string.isRequired,
+ htmlStr: PropTypes.string,
+ })).isRequired,
+};
+
+View.defaultProps = {
+ user: null,
+};
+
+const mapStateToProps = state => ({
+ schedules: state.view.schedules,
+});
+
+export default withRouter(connect(mapStateToProps)(View));
diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js
index 2ad65a6..ea8cd10 100644
--- a/src/client/react/components/page/User.js
+++ b/src/client/react/components/page/User.js
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import Search from '../container/Search';
+import View from '../container/View';
import users from '../../users';
const App = ({ match }) => {
@@ -15,6 +16,7 @@ const App = ({ match }) => {
return (
<div>
<Search urlUser={user} />
+ <View user={user} />
</div>
);
};
diff --git a/src/client/react/index.js b/src/client/react/index.js
index ffa5403..a7006d4 100644
--- a/src/client/react/index.js
+++ b/src/client/react/index.js
@@ -13,7 +13,7 @@ import User from './components/page/User';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
- composeEnhancers(applyMiddleware(logger, thunk)),
+ composeEnhancers(applyMiddleware(thunk, logger)),
);
ReactDOM.render(
diff --git a/src/client/react/reducers.js b/src/client/react/reducers.js
index 9fdf2c4..fb97228 100644
--- a/src/client/react/reducers.js
+++ b/src/client/react/reducers.js
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux';
import search from './reducers/search';
+import view from './reducers/view';
const rootReducer = combineReducers({
search,
+ view,
});
export default rootReducer;
diff --git a/src/client/react/reducers/view.js b/src/client/react/reducers/view.js
new file mode 100644
index 0000000..276d8ae
--- /dev/null
+++ b/src/client/react/reducers/view.js
@@ -0,0 +1,38 @@
+const DEFAULT_STATE = {
+ schedules: {},
+};
+
+const view = (state = DEFAULT_STATE, action) => {
+ switch (action.type) {
+ case 'VIEW/FETCH_SCHEDULE_REQUEST':
+ return {
+ ...state,
+ schedules: {
+ ...state.schedules,
+ [action.user]: {
+ state: 'fetching',
+ },
+ },
+ };
+ case 'VIEW/FETCH_SCHEDULE_SUCCESS':
+ return {
+ ...state,
+ schedules: {
+ ...state.schedules,
+ [action.user]: {
+ ...state.schedules[action.user],
+ state: 'finished',
+ htmlStr: action.htmlStr,
+ },
+ },
+ };
+ default:
+ return state;
+ }
+};
+
+export default view;
+
+export const _test = {
+ DEFAULT_STATE,
+};