From 1b3f4ea79f947558573fbce5a2e2d0c2c5dd6a8d Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 17 Jan 2018 16:26:04 +0100 Subject: Add view code --- src/client/react/actions/view.js | 28 ++++++++++ src/client/react/components/container/Search.js | 21 ++++++-- src/client/react/components/container/View.js | 70 +++++++++++++++++++++++++ src/client/react/components/page/User.js | 2 + src/client/react/index.js | 2 +- src/client/react/reducers.js | 2 + src/client/react/reducers/view.js | 38 ++++++++++++++ 7 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 src/client/react/actions/view.js create mode 100644 src/client/react/components/container/View.js create mode 100644 src/client/react/reducers/view.js (limited to 'src/client') 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 ( +
+ Loading... +
+ ); + } + + const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user].htmlStr); + + return ( + // eslint-disable-next-line react/no-danger +
+ ); + } +} + +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 (
+
); }; 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, +}; -- cgit v1.1