From 4ce420528dd747021f7fa51483710388f5733724 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 01:01:36 +0100 Subject: Add Search container --- src/client/react/components/container/Search.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/client/react/components/container/Search.js (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js new file mode 100644 index 0000000..1b5a41d --- /dev/null +++ b/src/client/react/components/container/Search.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux'; +import { type } from '../../actions'; +import PresentationalSearch from '../presentational/Search'; + +const mapStateToProps = state => ({ + results: state.searchResults, +}); + +const mapDispatchToProps = dispatch => ({ + onType: (event) => { + dispatch(type(event.target.value)); + }, +}); + +const Search = connect( + mapStateToProps, + mapDispatchToProps, +)(PresentationalSearch); + +export default Search; -- cgit v1.1 From b7fab958633456346d67c9cdd68eef05572882ab Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 01:07:11 +0100 Subject: Add redux devtools support --- src/client/react/components/container/Search.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 1b5a41d..0722128 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -4,6 +4,7 @@ import PresentationalSearch from '../presentational/Search'; const mapStateToProps = state => ({ results: state.searchResults, + value: state.searchInput, }); const mapDispatchToProps = dispatch => ({ -- cgit v1.1 From 7bd3b6766536e33146bb55506c79619a1ab7d3b3 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 11:10:05 +0100 Subject: Move reducers and actions into seperate folders --- src/client/react/components/container/Search.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 0722128..ddfb0a6 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; -import { type } from '../../actions'; +import { type } from '../../actions/search'; import PresentationalSearch from '../presentational/Search'; const mapStateToProps = state => ({ - results: state.searchResults, - value: state.searchInput, + results: state.search.searchResults, + value: state.search.searchInput, }); const mapDispatchToProps = dispatch => ({ -- cgit v1.1 From 1286c6556115f80218a4828d29b288f56b3d795f Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 11:13:08 +0100 Subject: Rename onType to onInputChange --- src/client/react/components/container/Search.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index ddfb0a6..2489084 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { type } from '../../actions/search'; +import { inputChange } from '../../actions/search'; import PresentationalSearch from '../presentational/Search'; const mapStateToProps = state => ({ @@ -8,8 +8,8 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ - onType: (event) => { - dispatch(type(event.target.value)); + onInputChange: (event) => { + dispatch(inputChange(event.target.value)); }, }); -- cgit v1.1 From 9f6a36d1f1a16c1a777a23fcc8c986c45ee0a116 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 13:25:46 +0100 Subject: Add some basic styling --- src/client/react/components/container/Search.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 2489084..206a6a1 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -1,16 +1,23 @@ import { connect } from 'react-redux'; -import { inputChange } from '../../actions/search'; +import { inputChange, focusChange } from '../../actions/search'; import PresentationalSearch from '../presentational/Search'; const mapStateToProps = state => ({ results: state.search.searchResults, value: state.search.searchInput, + hasFocus: state.search.hasFocus, }); const mapDispatchToProps = dispatch => ({ onInputChange: (event) => { dispatch(inputChange(event.target.value)); }, + onFocus: () => { + dispatch(focusChange(true)); + }, + onBlur: () => { + dispatch(focusChange(false)); + }, }); const Search = connect( -- cgit v1.1 From 797fb96cd0001d6c739c89507befc73d3d8a6614 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 15:58:25 +0100 Subject: Show user icon instead of search icon on exact match --- src/client/react/components/container/Search.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 206a6a1..70b3685 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -6,6 +6,7 @@ const mapStateToProps = state => ({ results: state.search.searchResults, value: state.search.searchInput, hasFocus: state.search.hasFocus, + exactMatch: state.search.exactMatch, }); const mapDispatchToProps = dispatch => ({ -- cgit v1.1 From ddccdcf22bb5008022e93c4789311fb2da95d7cf Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 19:24:53 +0100 Subject: Refactor state names --- src/client/react/components/container/Search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 70b3685..f17a517 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -3,8 +3,8 @@ import { inputChange, focusChange } from '../../actions/search'; import PresentationalSearch from '../presentational/Search'; const mapStateToProps = state => ({ - results: state.search.searchResults, - value: state.search.searchInput, + results: state.search.results, + value: state.search.input, hasFocus: state.search.hasFocus, exactMatch: state.search.exactMatch, }); -- cgit v1.1 From 6ef491badd4ac0190ab17cc41ebd27abbf87c896 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Mon, 11 Dec 2017 19:26:56 +0100 Subject: Select search input on focus --- src/client/react/components/container/Search.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index f17a517..4517191 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -15,6 +15,7 @@ const mapDispatchToProps = dispatch => ({ }, onFocus: () => { dispatch(focusChange(true)); + document.querySelector('#search__input').select(); }, onBlur: () => { dispatch(focusChange(false)); -- cgit v1.1 From 00d4e5241e220f8f1df4f3d5b796bc70d5fcd3fe Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Mon, 11 Dec 2017 19:43:23 +0100 Subject: Move PresentationalSearch back to the container --- src/client/react/components/container/Search.js | 30 --------- src/client/react/components/container/Search.jsx | 85 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 30 deletions(-) delete mode 100644 src/client/react/components/container/Search.js create mode 100644 src/client/react/components/container/Search.jsx (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js deleted file mode 100644 index 4517191..0000000 --- a/src/client/react/components/container/Search.js +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux'; -import { inputChange, focusChange } from '../../actions/search'; -import PresentationalSearch from '../presentational/Search'; - -const mapStateToProps = state => ({ - results: state.search.results, - value: state.search.input, - hasFocus: state.search.hasFocus, - exactMatch: state.search.exactMatch, -}); - -const mapDispatchToProps = dispatch => ({ - onInputChange: (event) => { - dispatch(inputChange(event.target.value)); - }, - onFocus: () => { - dispatch(focusChange(true)); - document.querySelector('#search__input').select(); - }, - onBlur: () => { - dispatch(focusChange(false)); - }, -}); - -const Search = connect( - mapStateToProps, - mapDispatchToProps, -)(PresentationalSearch); - -export default Search; diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx new file mode 100644 index 0000000..7a2822f --- /dev/null +++ b/src/client/react/components/container/Search.jsx @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; + +import SearchIcon from 'react-icons/lib/md/search'; + +import { inputChange, focusChange } from '../../actions/search'; + +import IconFromUserType from '../presentational/IconFromUserType'; +import Result from '../presentational/Result'; + +const userShape = { + value: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, +}; + +const Search = ({ + onInputChange, + onFocus, + onBlur, + hasFocus, + value, + results, + exactMatch, +}) => ( +
0 })}> +
+ {/* Show the icon from the exact match if there is an exact match, otherwise show the search icon. */} +
+ } + /> +
+ +
+ {results.map(user => ( + + ))} +
+); + +Search.propTypes = { + onInputChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + onBlur: PropTypes.func.isRequired, + hasFocus: PropTypes.bool.isRequired, + value: PropTypes.string.isRequired, + results: PropTypes.arrayOf(PropTypes.shape(userShape)).isRequired, + exactMatch: PropTypes.shape(userShape), +}; + +Search.defaultProps = { + exactMatch: null, +}; + +const mapStateToProps = state => ({ + results: state.search.results, + value: state.search.input, + hasFocus: state.search.hasFocus, + exactMatch: state.search.exactMatch, +}); + +const mapDispatchToProps = dispatch => ({ + onInputChange: (event) => { + dispatch(inputChange(event.target.value)); + }, + onFocus: () => { + dispatch(focusChange(true)); + document.querySelector('#search__input').select(); + }, + onBlur: () => { + dispatch(focusChange(false)); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Search); -- cgit v1.1 From 2cac093369ecb965ddbef60e6dc2aa6e50f0e937 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Mon, 11 Dec 2017 20:36:51 +0100 Subject: Refactor Results out of Search --- src/client/react/components/container/Results.jsx | 15 +++++++++++++++ src/client/react/components/container/Search.jsx | 13 ++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 src/client/react/components/container/Results.jsx (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx new file mode 100644 index 0000000..04d1f84 --- /dev/null +++ b/src/client/react/components/container/Results.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Result from '../presentational/Result'; + +const Results = (({ results }) => ( + results.map(user => ( + + )) +)); + +const mapStateToProps = state => ({ + results: state.search.results, +}); + +export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 7a2822f..7890423 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -7,8 +7,8 @@ import SearchIcon from 'react-icons/lib/md/search'; import { inputChange, focusChange } from '../../actions/search'; +import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; -import Result from '../presentational/Result'; const userShape = { value: PropTypes.string.isRequired, @@ -21,16 +21,14 @@ const Search = ({ onBlur, hasFocus, value, - results, exactMatch, }) => ( -
0 })}> +
- {/* Show the icon from the exact match if there is an exact match, otherwise show the search icon. */}
} + defaultIcon={} />
- {results.map(user => ( - - ))} +
); @@ -54,7 +50,6 @@ Search.propTypes = { onBlur: PropTypes.func.isRequired, hasFocus: PropTypes.bool.isRequired, value: PropTypes.string.isRequired, - results: PropTypes.arrayOf(PropTypes.shape(userShape)).isRequired, exactMatch: PropTypes.shape(userShape), }; -- cgit v1.1 From 2216d1ceed02e54620f16fb826e5947b6c2cb9bf Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Mon, 11 Dec 2017 20:43:07 +0100 Subject: Add line between results and search --- src/client/react/components/container/Results.jsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx index 04d1f84..1c38c8b 100644 --- a/src/client/react/components/container/Results.jsx +++ b/src/client/react/components/container/Results.jsx @@ -1,13 +1,28 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import classnames from 'classnames'; import Result from '../presentational/Result'; const Results = (({ results }) => ( - results.map(user => ( - - )) +
0, + })} + > + {results.map(user => ( + + ))} +
)); +Results.propTypes = { + results: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + value: PropTypes.value, + })).isRequired, +}; + const mapStateToProps = state => ({ results: state.search.results, }); -- cgit v1.1 From b00e556ebff50558e6532683ff2763825c51646f Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Mon, 11 Dec 2017 20:52:17 +0100 Subject: Move hasFocus to internal state --- src/client/react/components/container/Search.jsx | 95 ++++++++++++++---------- 1 file changed, 57 insertions(+), 38 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 7890423..a26c277 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -15,40 +15,67 @@ const userShape = { type: PropTypes.string.isRequired, }; -const Search = ({ - onInputChange, - onFocus, - onBlur, - hasFocus, - value, - exactMatch, -}) => ( -
-
-
- } - /> +class Search extends React.Component { + constructor(props) { + super(props); + + this.state = { + hasFocus: false, + }; + + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + } + + onFocus() { + this.setState({ + hasFocus: true, + }); + } + + onBlur() { + this.setState({ + hasFocus: false, + }); + } + + render() { + const { + onInputChange, + value, + exactMatch, + } = this.props; + + const { + hasFocus, + } = this.state; + + return ( +
+
+
+ } + /> +
+ +
+
- -
- -
-); + ); + } +} Search.propTypes = { onInputChange: PropTypes.func.isRequired, - onFocus: PropTypes.func.isRequired, - onBlur: PropTypes.func.isRequired, - hasFocus: PropTypes.bool.isRequired, value: PropTypes.string.isRequired, exactMatch: PropTypes.shape(userShape), }; @@ -60,7 +87,6 @@ Search.defaultProps = { const mapStateToProps = state => ({ results: state.search.results, value: state.search.input, - hasFocus: state.search.hasFocus, exactMatch: state.search.exactMatch, }); @@ -68,13 +94,6 @@ const mapDispatchToProps = dispatch => ({ onInputChange: (event) => { dispatch(inputChange(event.target.value)); }, - onFocus: () => { - dispatch(focusChange(true)); - document.querySelector('#search__input').select(); - }, - onBlur: () => { - dispatch(focusChange(false)); - }, }); export default connect(mapStateToProps, mapDispatchToProps)(Search); -- cgit v1.1 From 41a01f056984dc74f47e0380e2fe28fa16a59ff7 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Dec 2017 12:19:37 +0100 Subject: Use inline dispatch --- src/client/react/components/container/Search.jsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index a26c277..e974bd9 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -5,7 +5,7 @@ import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; -import { inputChange, focusChange } from '../../actions/search'; +import { inputChange } from '../../actions/search'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; @@ -41,9 +41,9 @@ class Search extends React.Component { render() { const { - onInputChange, value, exactMatch, + dispatch, } = this.props; const { @@ -61,7 +61,7 @@ class Search extends React.Component {
dispatch(inputChange(event.target.value))} value={value} placeholder="Zoeken" onFocus={this.onFocus} @@ -75,9 +75,9 @@ class Search extends React.Component { } Search.propTypes = { - onInputChange: PropTypes.func.isRequired, value: PropTypes.string.isRequired, exactMatch: PropTypes.shape(userShape), + dispatch: PropTypes.func.isRequired, }; Search.defaultProps = { @@ -90,10 +90,4 @@ const mapStateToProps = state => ({ exactMatch: state.search.exactMatch, }); -const mapDispatchToProps = dispatch => ({ - onInputChange: (event) => { - dispatch(inputChange(event.target.value)); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Search); +export default connect(mapStateToProps)(Search); -- cgit v1.1 From fe27a0819a60caaa69b059f0c86d95ab0c4084b7 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Dec 2017 12:26:36 +0100 Subject: Prepair changeSelectedResult --- src/client/react/components/container/Search.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index e974bd9..7e33e84 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -42,7 +42,8 @@ class Search extends React.Component { render() { const { value, - exactMatch, + selectedResult, + isExactMatch, dispatch, } = this.props; @@ -55,7 +56,7 @@ class Search extends React.Component {
} />
@@ -76,18 +77,20 @@ class Search extends React.Component { Search.propTypes = { value: PropTypes.string.isRequired, - exactMatch: PropTypes.shape(userShape), + selectedResult: PropTypes.shape(userShape), + isExactMatch: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, }; Search.defaultProps = { - exactMatch: null, + selectedResult: null, }; const mapStateToProps = state => ({ results: state.search.results, value: state.search.input, - exactMatch: state.search.exactMatch, + selectedResult: state.search.selectedResult, + isExactMatch: state.search.isExactMatch, }); export default connect(mapStateToProps)(Search); -- cgit v1.1 From 503ab5c66ab524dfe36aed84a01899cd07ed2bc5 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Dec 2017 15:53:19 +0100 Subject: Allow selection of result using keyboard --- src/client/react/components/container/Results.jsx | 15 ++++++++++++--- src/client/react/components/container/Search.jsx | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx index 1c38c8b..9be2639 100644 --- a/src/client/react/components/container/Results.jsx +++ b/src/client/react/components/container/Results.jsx @@ -4,14 +4,14 @@ import { connect } from 'react-redux'; import classnames from 'classnames'; import Result from '../presentational/Result'; -const Results = (({ results }) => ( +const Results = (({ results, selectedResult }) => (
0, })} > {results.map(user => ( - + ))}
)); @@ -19,12 +19,21 @@ const Results = (({ results }) => ( Results.propTypes = { results: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.string, - value: PropTypes.value, + value: PropTypes.string, })).isRequired, + selectedResult: PropTypes.shape({ + type: PropTypes.string, + value: PropTypes.string, + }), +}; + +Results.defaultProps = { + selectedResult: null, }; const mapStateToProps = state => ({ results: state.search.results, + selectedResult: state.search.selectedResult, }); export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 7e33e84..50917dd 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -5,7 +5,7 @@ import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; -import { inputChange } from '../../actions/search'; +import { inputChange, changeSelectedResult } from '../../actions/search'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; @@ -25,6 +25,7 @@ class Search extends React.Component { this.onFocus = this.onFocus.bind(this); this.onBlur = this.onBlur.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); } onFocus() { @@ -39,6 +40,22 @@ class Search extends React.Component { }); } + onKeyDown(event) { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + switch (event.key) { + case 'ArrowUp': + this.props.dispatch(changeSelectedResult(-1)); + break; + case 'ArrowDown': + this.props.dispatch(changeSelectedResult(+1)); + break; + default: + throw new Error('This should never happen... pls?'); + } + } + } + render() { const { value, @@ -63,6 +80,7 @@ class Search extends React.Component { dispatch(inputChange(event.target.value))} + onKeyDown={this.onKeyDown} value={value} placeholder="Zoeken" onFocus={this.onFocus} -- cgit v1.1 From 778dfdc728a101fca9ece3a14e590d3b8e1d43e1 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 14 Dec 2017 12:32:07 +0100 Subject: Use id's instead of user objects --- src/client/react/components/container/Results.jsx | 20 ++++++++------------ src/client/react/components/container/Search.jsx | 10 +++------- 2 files changed, 11 insertions(+), 19 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx index 9be2639..911ea27 100644 --- a/src/client/react/components/container/Results.jsx +++ b/src/client/react/components/container/Results.jsx @@ -4,27 +4,22 @@ import { connect } from 'react-redux'; import classnames from 'classnames'; import Result from '../presentational/Result'; -const Results = (({ results, selectedResult }) => ( +const Results = (({ results, isExactMatch, selectedResult }) => (
0, + 'search__results--has-results': !isExactMatch && results.length > 0, })} > - {results.map(user => ( - + {!isExactMatch && results.map(userId => ( + ))}
)); Results.propTypes = { - results: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.string, - value: PropTypes.string, - })).isRequired, - selectedResult: PropTypes.shape({ - type: PropTypes.string, - value: PropTypes.string, - }), + results: PropTypes.arrayOf(PropTypes.string).isRequired, + isExactMatch: PropTypes.bool.isRequired, + selectedResult: PropTypes.string, }; Results.defaultProps = { @@ -33,6 +28,7 @@ Results.defaultProps = { const mapStateToProps = state => ({ results: state.search.results, + isExactMatch: state.search.isExactMatch, selectedResult: state.search.selectedResult, }); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 50917dd..babe0c4 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -7,14 +7,10 @@ import SearchIcon from 'react-icons/lib/md/search'; import { inputChange, changeSelectedResult } from '../../actions/search'; +import users from '../../users'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; -const userShape = { - value: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}; - class Search extends React.Component { constructor(props) { super(props); @@ -73,7 +69,7 @@ class Search extends React.Component {
} />
@@ -95,7 +91,7 @@ class Search extends React.Component { Search.propTypes = { value: PropTypes.string.isRequired, - selectedResult: PropTypes.shape(userShape), + selectedResult: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, }; -- cgit v1.1 From bb5b3629445ed6a52c3a088dbc2dd08b7a326f8a Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 14 Dec 2017 16:24:10 +0100 Subject: Remove input value from redux --- src/client/react/components/container/Search.jsx | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index babe0c4..e49e6a7 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -54,7 +54,6 @@ class Search extends React.Component { render() { const { - value, selectedResult, isExactMatch, dispatch, @@ -77,7 +76,6 @@ class Search extends React.Component { id="search__input" onChange={event => dispatch(inputChange(event.target.value))} onKeyDown={this.onKeyDown} - value={value} placeholder="Zoeken" onFocus={this.onFocus} onBlur={this.onBlur} @@ -90,7 +88,6 @@ class Search extends React.Component { } Search.propTypes = { - value: PropTypes.string.isRequired, selectedResult: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, @@ -102,7 +99,6 @@ Search.defaultProps = { const mapStateToProps = state => ({ results: state.search.results, - value: state.search.input, selectedResult: state.search.selectedResult, isExactMatch: state.search.isExactMatch, }); -- cgit v1.1 From 569b2969d530f08e55798c5cb3079948c7c037cd Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 14 Dec 2017 18:54:00 +0100 Subject: Use .js extention instead of .jsx --- src/client/react/components/container/Results.js | 35 +++++++ src/client/react/components/container/Results.jsx | 35 ------- src/client/react/components/container/Search.js | 106 ++++++++++++++++++++++ src/client/react/components/container/Search.jsx | 106 ---------------------- 4 files changed, 141 insertions(+), 141 deletions(-) create mode 100644 src/client/react/components/container/Results.js delete mode 100644 src/client/react/components/container/Results.jsx create mode 100644 src/client/react/components/container/Search.js delete mode 100644 src/client/react/components/container/Search.jsx (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js new file mode 100644 index 0000000..911ea27 --- /dev/null +++ b/src/client/react/components/container/Results.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; +import Result from '../presentational/Result'; + +const Results = (({ results, isExactMatch, selectedResult }) => ( +
0, + })} + > + {!isExactMatch && results.map(userId => ( + + ))} +
+)); + +Results.propTypes = { + results: PropTypes.arrayOf(PropTypes.string).isRequired, + isExactMatch: PropTypes.bool.isRequired, + selectedResult: PropTypes.string, +}; + +Results.defaultProps = { + selectedResult: null, +}; + +const mapStateToProps = state => ({ + results: state.search.results, + isExactMatch: state.search.isExactMatch, + selectedResult: state.search.selectedResult, +}); + +export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx deleted file mode 100644 index 911ea27..0000000 --- a/src/client/react/components/container/Results.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classnames from 'classnames'; -import Result from '../presentational/Result'; - -const Results = (({ results, isExactMatch, selectedResult }) => ( -
0, - })} - > - {!isExactMatch && results.map(userId => ( - - ))} -
-)); - -Results.propTypes = { - results: PropTypes.arrayOf(PropTypes.string).isRequired, - isExactMatch: PropTypes.bool.isRequired, - selectedResult: PropTypes.string, -}; - -Results.defaultProps = { - selectedResult: null, -}; - -const mapStateToProps = state => ({ - results: state.search.results, - isExactMatch: state.search.isExactMatch, - selectedResult: state.search.selectedResult, -}); - -export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js new file mode 100644 index 0000000..e49e6a7 --- /dev/null +++ b/src/client/react/components/container/Search.js @@ -0,0 +1,106 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; + +import SearchIcon from 'react-icons/lib/md/search'; + +import { inputChange, changeSelectedResult } from '../../actions/search'; + +import users from '../../users'; +import Results from './Results'; +import IconFromUserType from '../presentational/IconFromUserType'; + +class Search extends React.Component { + constructor(props) { + super(props); + + this.state = { + hasFocus: false, + }; + + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + } + + onFocus() { + this.setState({ + hasFocus: true, + }); + } + + onBlur() { + this.setState({ + hasFocus: false, + }); + } + + onKeyDown(event) { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + switch (event.key) { + case 'ArrowUp': + this.props.dispatch(changeSelectedResult(-1)); + break; + case 'ArrowDown': + this.props.dispatch(changeSelectedResult(+1)); + break; + default: + throw new Error('This should never happen... pls?'); + } + } + } + + render() { + const { + selectedResult, + isExactMatch, + dispatch, + } = this.props; + + const { + hasFocus, + } = this.state; + + return ( +
+
+
+ } + /> +
+ dispatch(inputChange(event.target.value))} + onKeyDown={this.onKeyDown} + placeholder="Zoeken" + onFocus={this.onFocus} + onBlur={this.onBlur} + /> +
+ +
+ ); + } +} + +Search.propTypes = { + selectedResult: PropTypes.string, + isExactMatch: PropTypes.bool.isRequired, + dispatch: PropTypes.func.isRequired, +}; + +Search.defaultProps = { + selectedResult: null, +}; + +const mapStateToProps = state => ({ + results: state.search.results, + selectedResult: state.search.selectedResult, + isExactMatch: state.search.isExactMatch, +}); + +export default connect(mapStateToProps)(Search); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx deleted file mode 100644 index e49e6a7..0000000 --- a/src/client/react/components/container/Search.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classnames from 'classnames'; - -import SearchIcon from 'react-icons/lib/md/search'; - -import { inputChange, changeSelectedResult } from '../../actions/search'; - -import users from '../../users'; -import Results from './Results'; -import IconFromUserType from '../presentational/IconFromUserType'; - -class Search extends React.Component { - constructor(props) { - super(props); - - this.state = { - hasFocus: false, - }; - - this.onFocus = this.onFocus.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - } - - onFocus() { - this.setState({ - hasFocus: true, - }); - } - - onBlur() { - this.setState({ - hasFocus: false, - }); - } - - onKeyDown(event) { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { - event.preventDefault(); - switch (event.key) { - case 'ArrowUp': - this.props.dispatch(changeSelectedResult(-1)); - break; - case 'ArrowDown': - this.props.dispatch(changeSelectedResult(+1)); - break; - default: - throw new Error('This should never happen... pls?'); - } - } - } - - render() { - const { - selectedResult, - isExactMatch, - dispatch, - } = this.props; - - const { - hasFocus, - } = this.state; - - return ( -
-
-
- } - /> -
- dispatch(inputChange(event.target.value))} - onKeyDown={this.onKeyDown} - placeholder="Zoeken" - onFocus={this.onFocus} - onBlur={this.onBlur} - /> -
- -
- ); - } -} - -Search.propTypes = { - selectedResult: PropTypes.string, - isExactMatch: PropTypes.bool.isRequired, - dispatch: PropTypes.func.isRequired, -}; - -Search.defaultProps = { - selectedResult: null, -}; - -const mapStateToProps = state => ({ - results: state.search.results, - selectedResult: state.search.selectedResult, - isExactMatch: state.search.isExactMatch, -}); - -export default connect(mapStateToProps)(Search); -- cgit v1.1 From f0c8cf0e79f003514fd65a70def5820205955a77 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 21 Dec 2017 12:06:41 +0100 Subject: Move to typescript --- src/client/react/components/container/Results.js | 35 ------ src/client/react/components/container/Results.tsx | 26 +++++ src/client/react/components/container/Search.js | 106 ----------------- src/client/react/components/container/Search.tsx | 131 ++++++++++++++++++++++ 4 files changed, 157 insertions(+), 141 deletions(-) delete mode 100644 src/client/react/components/container/Results.js create mode 100644 src/client/react/components/container/Results.tsx delete mode 100644 src/client/react/components/container/Search.js create mode 100644 src/client/react/components/container/Search.tsx (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js deleted file mode 100644 index 911ea27..0000000 --- a/src/client/react/components/container/Results.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classnames from 'classnames'; -import Result from '../presentational/Result'; - -const Results = (({ results, isExactMatch, selectedResult }) => ( -
0, - })} - > - {!isExactMatch && results.map(userId => ( - - ))} -
-)); - -Results.propTypes = { - results: PropTypes.arrayOf(PropTypes.string).isRequired, - isExactMatch: PropTypes.bool.isRequired, - selectedResult: PropTypes.string, -}; - -Results.defaultProps = { - selectedResult: null, -}; - -const mapStateToProps = state => ({ - results: state.search.results, - isExactMatch: state.search.isExactMatch, - selectedResult: state.search.selectedResult, -}); - -export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Results.tsx b/src/client/react/components/container/Results.tsx new file mode 100644 index 0000000..21d3378 --- /dev/null +++ b/src/client/react/components/container/Results.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import * as classnames from 'classnames'; +import Result from '../presentational/Result'; +import { User } from '../../users'; +import { State } from '../../reducers'; + +const Results: React.StatelessComponent<{ results: string[], isExactMatch: boolean, selectedResult: string }> = (props) => ( +
0, + })} + > + {!props.isExactMatch && props.results.map(userId => ( + + ))} +
+); + +const mapStateToProps = (state: State) => ({ + results: state.search.results, + isExactMatch: state.search.isExactMatch, + selectedResult: state.search.selectedResult, +}); + +export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js deleted file mode 100644 index e49e6a7..0000000 --- a/src/client/react/components/container/Search.js +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classnames from 'classnames'; - -import SearchIcon from 'react-icons/lib/md/search'; - -import { inputChange, changeSelectedResult } from '../../actions/search'; - -import users from '../../users'; -import Results from './Results'; -import IconFromUserType from '../presentational/IconFromUserType'; - -class Search extends React.Component { - constructor(props) { - super(props); - - this.state = { - hasFocus: false, - }; - - this.onFocus = this.onFocus.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - } - - onFocus() { - this.setState({ - hasFocus: true, - }); - } - - onBlur() { - this.setState({ - hasFocus: false, - }); - } - - onKeyDown(event) { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { - event.preventDefault(); - switch (event.key) { - case 'ArrowUp': - this.props.dispatch(changeSelectedResult(-1)); - break; - case 'ArrowDown': - this.props.dispatch(changeSelectedResult(+1)); - break; - default: - throw new Error('This should never happen... pls?'); - } - } - } - - render() { - const { - selectedResult, - isExactMatch, - dispatch, - } = this.props; - - const { - hasFocus, - } = this.state; - - return ( -
-
-
- } - /> -
- dispatch(inputChange(event.target.value))} - onKeyDown={this.onKeyDown} - placeholder="Zoeken" - onFocus={this.onFocus} - onBlur={this.onBlur} - /> -
- -
- ); - } -} - -Search.propTypes = { - selectedResult: PropTypes.string, - isExactMatch: PropTypes.bool.isRequired, - dispatch: PropTypes.func.isRequired, -}; - -Search.defaultProps = { - selectedResult: null, -}; - -const mapStateToProps = state => ({ - results: state.search.results, - selectedResult: state.search.selectedResult, - isExactMatch: state.search.isExactMatch, -}); - -export default connect(mapStateToProps)(Search); diff --git a/src/client/react/components/container/Search.tsx b/src/client/react/components/container/Search.tsx new file mode 100644 index 0000000..fdd6c83 --- /dev/null +++ b/src/client/react/components/container/Search.tsx @@ -0,0 +1,131 @@ +import * as React from 'react'; +import { Dispatch } from 'redux'; +import { connect } from 'react-redux'; +import * as classnames from 'classnames'; + +import SearchIcon = require('react-icons/lib/md/search'); + +import { inputChange, changeSelectedResult } from '../../actions/search'; +import { Action } from '../../reducers/search'; +import { State } from '../../reducers'; + +import users from '../../users'; +import Results from './Results'; +import IconFromUserType from '../presentational/IconFromUserType'; + +interface SearchStatehProps { + selectedResult: string, + isExactMatch: boolean, +} + +interface SearchDispatchProps { + changeSelectedResult(relativeChange: 1 | -1): void, + inputChange(typedValue: string): void, +} + +class Search extends React.Component { + constructor(props: SearchStatehProps & SearchDispatchProps) { + super(props); + + this.state = { + hasFocus: false, + }; + + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + } + + onFocus() { + this.setState({ + hasFocus: true, + }); + } + + onBlur() { + this.setState({ + hasFocus: false, + }); + } + + onKeyDown(event: React.KeyboardEvent) { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + switch (event.key) { + case 'ArrowUp': + this.props.changeSelectedResult(-1); + break; + case 'ArrowDown': + this.props.changeSelectedResult(+1); + break; + default: + throw new Error('This should never happen... pls?'); + } + } + } + + render() { + const { + selectedResult, + isExactMatch, + inputChange, + } = this.props; + + const { + hasFocus, + } = this.state; + + return ( +
+
+
+ } + /> +
+ inputChange(event.target.value)} + onKeyDown={this.onKeyDown} + placeholder="Zoeken" + onFocus={this.onFocus} + onBlur={this.onBlur} + /> +
+ +
+ ); + } +} + +// Search.propTypes = { +// selectedResult: PropTypes.string, +// isExactMatch: PropTypes.bool.isRequired, +// dispatch: PropTypes.func.isRequired, +// }; + +// Search.defaultProps = { +// selectedResult: null, +// }; + +const mapStateToProps = (state: State):SearchStatehProps => ({ + selectedResult: state.search.selectedResult, + isExactMatch: state.search.isExactMatch, +}); + +// const mapDispatchToProps = { +// inputChange, +// changeSelectedResult, +// }; + +const mapDispatchToProps = (dispatch: any): SearchDispatchProps => ({ + inputChange(typedValue) { + dispatch(inputChange(typedValue)); + }, + changeSelectedResult(relativeChange) { + dispatch(changeSelectedResult(relativeChange)) + } +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Search); -- cgit v1.1 From 4ca30295d7d9f3dd7ba2e105952ff627f6b702a4 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 21 Dec 2017 13:10:05 +0100 Subject: Add strict typing Except for functions because of https://github.com/reactjs/redux/issues/2709 --- src/client/react/components/container/Search.tsx | 27 ++++++------------------ 1 file changed, 6 insertions(+), 21 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.tsx b/src/client/react/components/container/Search.tsx index fdd6c83..b22c26e 100644 --- a/src/client/react/components/container/Search.tsx +++ b/src/client/react/components/container/Search.tsx @@ -13,8 +13,8 @@ import users from '../../users'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; -interface SearchStatehProps { - selectedResult: string, +interface SearchStateProps { + selectedResult: string | null, isExactMatch: boolean, } @@ -23,8 +23,8 @@ interface SearchDispatchProps { inputChange(typedValue: string): void, } -class Search extends React.Component { - constructor(props: SearchStatehProps & SearchDispatchProps) { +class Search extends React.Component { + constructor(props: SearchStateProps & SearchDispatchProps) { super(props); this.state = { @@ -80,7 +80,7 @@ class Search extends React.Component
} />
@@ -99,26 +99,11 @@ class Search extends React.Component ({ +const mapStateToProps = (state: State):SearchStateProps => ({ selectedResult: state.search.selectedResult, isExactMatch: state.search.isExactMatch, }); -// const mapDispatchToProps = { -// inputChange, -// changeSelectedResult, -// }; - const mapDispatchToProps = (dispatch: any): SearchDispatchProps => ({ inputChange(typedValue) { dispatch(inputChange(typedValue)); -- cgit v1.1 From 95041dffbd23fe81802efd5fb25cffe492cdb551 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 12:11:05 +0100 Subject: Revert "Add strict typing" This reverts commit 4ca30295d7d9f3dd7ba2e105952ff627f6b702a4. --- src/client/react/components/container/Search.tsx | 27 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.tsx b/src/client/react/components/container/Search.tsx index b22c26e..fdd6c83 100644 --- a/src/client/react/components/container/Search.tsx +++ b/src/client/react/components/container/Search.tsx @@ -13,8 +13,8 @@ import users from '../../users'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; -interface SearchStateProps { - selectedResult: string | null, +interface SearchStatehProps { + selectedResult: string, isExactMatch: boolean, } @@ -23,8 +23,8 @@ interface SearchDispatchProps { inputChange(typedValue: string): void, } -class Search extends React.Component { - constructor(props: SearchStateProps & SearchDispatchProps) { +class Search extends React.Component { + constructor(props: SearchStatehProps & SearchDispatchProps) { super(props); this.state = { @@ -80,7 +80,7 @@ class Search extends React.Component
} />
@@ -99,11 +99,26 @@ class Search extends React.Component ({ +// Search.propTypes = { +// selectedResult: PropTypes.string, +// isExactMatch: PropTypes.bool.isRequired, +// dispatch: PropTypes.func.isRequired, +// }; + +// Search.defaultProps = { +// selectedResult: null, +// }; + +const mapStateToProps = (state: State):SearchStatehProps => ({ selectedResult: state.search.selectedResult, isExactMatch: state.search.isExactMatch, }); +// const mapDispatchToProps = { +// inputChange, +// changeSelectedResult, +// }; + const mapDispatchToProps = (dispatch: any): SearchDispatchProps => ({ inputChange(typedValue) { dispatch(inputChange(typedValue)); -- cgit v1.1 From 77dccd31b32ee0a9a53b2186bae231069c5ab152 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 12:11:19 +0100 Subject: Revert "Move to typescript" This reverts commit f0c8cf0e79f003514fd65a70def5820205955a77. --- src/client/react/components/container/Results.js | 35 ++++++ src/client/react/components/container/Results.tsx | 26 ----- src/client/react/components/container/Search.js | 106 +++++++++++++++++ src/client/react/components/container/Search.tsx | 131 ---------------------- 4 files changed, 141 insertions(+), 157 deletions(-) create mode 100644 src/client/react/components/container/Results.js delete mode 100644 src/client/react/components/container/Results.tsx create mode 100644 src/client/react/components/container/Search.js delete mode 100644 src/client/react/components/container/Search.tsx (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js new file mode 100644 index 0000000..911ea27 --- /dev/null +++ b/src/client/react/components/container/Results.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; +import Result from '../presentational/Result'; + +const Results = (({ results, isExactMatch, selectedResult }) => ( +
0, + })} + > + {!isExactMatch && results.map(userId => ( + + ))} +
+)); + +Results.propTypes = { + results: PropTypes.arrayOf(PropTypes.string).isRequired, + isExactMatch: PropTypes.bool.isRequired, + selectedResult: PropTypes.string, +}; + +Results.defaultProps = { + selectedResult: null, +}; + +const mapStateToProps = state => ({ + results: state.search.results, + isExactMatch: state.search.isExactMatch, + selectedResult: state.search.selectedResult, +}); + +export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Results.tsx b/src/client/react/components/container/Results.tsx deleted file mode 100644 index 21d3378..0000000 --- a/src/client/react/components/container/Results.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import { connect } from 'react-redux'; -import * as classnames from 'classnames'; -import Result from '../presentational/Result'; -import { User } from '../../users'; -import { State } from '../../reducers'; - -const Results: React.StatelessComponent<{ results: string[], isExactMatch: boolean, selectedResult: string }> = (props) => ( -
0, - })} - > - {!props.isExactMatch && props.results.map(userId => ( - - ))} -
-); - -const mapStateToProps = (state: State) => ({ - results: state.search.results, - isExactMatch: state.search.isExactMatch, - selectedResult: state.search.selectedResult, -}); - -export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js new file mode 100644 index 0000000..e49e6a7 --- /dev/null +++ b/src/client/react/components/container/Search.js @@ -0,0 +1,106 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; + +import SearchIcon from 'react-icons/lib/md/search'; + +import { inputChange, changeSelectedResult } from '../../actions/search'; + +import users from '../../users'; +import Results from './Results'; +import IconFromUserType from '../presentational/IconFromUserType'; + +class Search extends React.Component { + constructor(props) { + super(props); + + this.state = { + hasFocus: false, + }; + + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + } + + onFocus() { + this.setState({ + hasFocus: true, + }); + } + + onBlur() { + this.setState({ + hasFocus: false, + }); + } + + onKeyDown(event) { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + switch (event.key) { + case 'ArrowUp': + this.props.dispatch(changeSelectedResult(-1)); + break; + case 'ArrowDown': + this.props.dispatch(changeSelectedResult(+1)); + break; + default: + throw new Error('This should never happen... pls?'); + } + } + } + + render() { + const { + selectedResult, + isExactMatch, + dispatch, + } = this.props; + + const { + hasFocus, + } = this.state; + + return ( +
+
+
+ } + /> +
+ dispatch(inputChange(event.target.value))} + onKeyDown={this.onKeyDown} + placeholder="Zoeken" + onFocus={this.onFocus} + onBlur={this.onBlur} + /> +
+ +
+ ); + } +} + +Search.propTypes = { + selectedResult: PropTypes.string, + isExactMatch: PropTypes.bool.isRequired, + dispatch: PropTypes.func.isRequired, +}; + +Search.defaultProps = { + selectedResult: null, +}; + +const mapStateToProps = state => ({ + results: state.search.results, + selectedResult: state.search.selectedResult, + isExactMatch: state.search.isExactMatch, +}); + +export default connect(mapStateToProps)(Search); diff --git a/src/client/react/components/container/Search.tsx b/src/client/react/components/container/Search.tsx deleted file mode 100644 index fdd6c83..0000000 --- a/src/client/react/components/container/Search.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import * as React from 'react'; -import { Dispatch } from 'redux'; -import { connect } from 'react-redux'; -import * as classnames from 'classnames'; - -import SearchIcon = require('react-icons/lib/md/search'); - -import { inputChange, changeSelectedResult } from '../../actions/search'; -import { Action } from '../../reducers/search'; -import { State } from '../../reducers'; - -import users from '../../users'; -import Results from './Results'; -import IconFromUserType from '../presentational/IconFromUserType'; - -interface SearchStatehProps { - selectedResult: string, - isExactMatch: boolean, -} - -interface SearchDispatchProps { - changeSelectedResult(relativeChange: 1 | -1): void, - inputChange(typedValue: string): void, -} - -class Search extends React.Component { - constructor(props: SearchStatehProps & SearchDispatchProps) { - super(props); - - this.state = { - hasFocus: false, - }; - - this.onFocus = this.onFocus.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - } - - onFocus() { - this.setState({ - hasFocus: true, - }); - } - - onBlur() { - this.setState({ - hasFocus: false, - }); - } - - onKeyDown(event: React.KeyboardEvent) { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { - event.preventDefault(); - switch (event.key) { - case 'ArrowUp': - this.props.changeSelectedResult(-1); - break; - case 'ArrowDown': - this.props.changeSelectedResult(+1); - break; - default: - throw new Error('This should never happen... pls?'); - } - } - } - - render() { - const { - selectedResult, - isExactMatch, - inputChange, - } = this.props; - - const { - hasFocus, - } = this.state; - - return ( -
-
-
- } - /> -
- inputChange(event.target.value)} - onKeyDown={this.onKeyDown} - placeholder="Zoeken" - onFocus={this.onFocus} - onBlur={this.onBlur} - /> -
- -
- ); - } -} - -// Search.propTypes = { -// selectedResult: PropTypes.string, -// isExactMatch: PropTypes.bool.isRequired, -// dispatch: PropTypes.func.isRequired, -// }; - -// Search.defaultProps = { -// selectedResult: null, -// }; - -const mapStateToProps = (state: State):SearchStatehProps => ({ - selectedResult: state.search.selectedResult, - isExactMatch: state.search.isExactMatch, -}); - -// const mapDispatchToProps = { -// inputChange, -// changeSelectedResult, -// }; - -const mapDispatchToProps = (dispatch: any): SearchDispatchProps => ({ - inputChange(typedValue) { - dispatch(inputChange(typedValue)); - }, - changeSelectedResult(relativeChange) { - dispatch(changeSelectedResult(relativeChange)) - } -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Search); -- cgit v1.1 From 13cac5dcd54bf87767396763226fd33719964d22 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 13:00:08 +0100 Subject: Go to user url on enter --- src/client/react/components/container/Search.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index e49e6a7..957f76e 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import classnames from 'classnames'; +import { withRouter } from 'react-router-dom'; import SearchIcon from 'react-icons/lib/md/search'; @@ -37,7 +38,7 @@ class Search extends React.Component { } onKeyDown(event) { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Enter') { event.preventDefault(); switch (event.key) { case 'ArrowUp': @@ -46,6 +47,11 @@ 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}`); + } + break; default: throw new Error('This should never happen... pls?'); } @@ -91,6 +97,9 @@ Search.propTypes = { selectedResult: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + }).isRequired, }; Search.defaultProps = { @@ -103,4 +112,4 @@ const mapStateToProps = state => ({ isExactMatch: state.search.isExactMatch, }); -export default connect(mapStateToProps)(Search); +export default connect(mapStateToProps)(withRouter(Search)); -- cgit v1.1 From 70a9b0be3782122750388c24eb98b0d45e6fc6d1 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 13:12:11 +0100 Subject: Save searchText in redux store --- src/client/react/components/container/Search.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 957f76e..06523be 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -62,6 +62,7 @@ class Search extends React.Component { const { selectedResult, isExactMatch, + searchText, dispatch, } = this.props; @@ -82,6 +83,7 @@ class Search extends React.Component { id="search__input" onChange={event => dispatch(inputChange(event.target.value))} onKeyDown={this.onKeyDown} + value={searchText} placeholder="Zoeken" onFocus={this.onFocus} onBlur={this.onBlur} @@ -96,6 +98,7 @@ class Search extends React.Component { Search.propTypes = { selectedResult: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, + searchText: PropTypes.string.isRequired, dispatch: PropTypes.func.isRequired, history: PropTypes.shape({ push: PropTypes.func.isRequired, @@ -108,6 +111,7 @@ Search.defaultProps = { const mapStateToProps = state => ({ results: state.search.results, + searchText: state.search.searchText, selectedResult: state.search.selectedResult, isExactMatch: state.search.isExactMatch, }); -- cgit v1.1 From c0aa588bc8f85b13b5a55ccd6cdf11bf99048a1c Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 15:42:04 +0100 Subject: Add user page --- src/client/react/components/container/Search.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 06523be..27b0563 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom'; import SearchIcon from 'react-icons/lib/md/search'; -import { inputChange, changeSelectedResult } from '../../actions/search'; +import { setUser, inputChange, changeSelectedResult } from '../../actions/search'; import users from '../../users'; import Results from './Results'; @@ -25,6 +25,10 @@ class Search extends React.Component { this.onKeyDown = this.onKeyDown.bind(this); } + componentDidMount() { + this.props.dispatch(setUser(this.props.urlUser)); + } + onFocus() { this.setState({ hasFocus: true, @@ -97,6 +101,7 @@ class Search extends React.Component { Search.propTypes = { selectedResult: PropTypes.string, + urlUser: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, searchText: PropTypes.string.isRequired, dispatch: PropTypes.func.isRequired, @@ -107,6 +112,7 @@ Search.propTypes = { Search.defaultProps = { selectedResult: null, + urlUser: null, }; const mapStateToProps = state => ({ @@ -116,4 +122,4 @@ const mapStateToProps = state => ({ isExactMatch: state.search.isExactMatch, }); -export default connect(mapStateToProps)(withRouter(Search)); +export default withRouter(connect(mapStateToProps)(Search)); -- cgit v1.1 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/components/container/Search.js | 21 ++++++-- src/client/react/components/container/View.js | 70 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/client/react/components/container/View.js (limited to 'src/client/react/components/container') 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)); -- cgit v1.1 From 3f228d0d84868e4a02aa7a62b09dde8092f2cc4c Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Fri, 19 Jan 2018 14:58:49 +0100 Subject: Add helpbox --- src/client/react/components/container/HelpBox.js | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/client/react/components/container/HelpBox.js (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/HelpBox.js b/src/client/react/components/container/HelpBox.js new file mode 100644 index 0000000..b4556a5 --- /dev/null +++ b/src/client/react/components/container/HelpBox.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +const HelpBox = ({ results }) => { + if (results.length > 0) { + return
; + } + + return ( +
+ Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in. +
+ ); +}; + +HelpBox.propTypes = { + results: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + +const mapStateToProps = state => ({ + results: state.search.results, +}); + +export default connect(mapStateToProps)(HelpBox); -- cgit v1.1 From 0bddf7661d7ece709a18f2d167b928749638f318 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 24 Jan 2018 15:44:31 +0100 Subject: Add animation to results --- src/client/react/components/container/HelpBox.js | 11 ++++++++--- src/client/react/components/container/Results.js | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/HelpBox.js b/src/client/react/components/container/HelpBox.js index b4556a5..a74b43c 100644 --- a/src/client/react/components/container/HelpBox.js +++ b/src/client/react/components/container/HelpBox.js @@ -2,24 +2,29 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -const HelpBox = ({ results }) => { - if (results.length > 0) { +const HelpBox = ({ results, searchText }) => { + if (results.length > 0 || searchText !== '') { return
; } return (
- Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in. +
+
+ Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in. +
); }; HelpBox.propTypes = { results: PropTypes.arrayOf(PropTypes.string).isRequired, + searchText: PropTypes.string.isRequired, }; const mapStateToProps = state => ({ results: state.search.results, + searchText: state.search.searchText, }); export default connect(mapStateToProps)(HelpBox); diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js index 911ea27..1fb5f44 100644 --- a/src/client/react/components/container/Results.js +++ b/src/client/react/components/container/Results.js @@ -9,6 +9,9 @@ const Results = (({ results, isExactMatch, selectedResult }) => ( className={classnames('search__results', { 'search__results--has-results': !isExactMatch && results.length > 0, })} + style={{ + minHeight: isExactMatch ? 0 : results.length * 54, + }} > {!isExactMatch && results.map(userId => ( -- cgit v1.1 From 16723546de81e29fa8a31acc4070df5acb182b24 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 24 Jan 2018 16:08:49 +0100 Subject: Add basic styling to user page --- src/client/react/components/container/Search.js | 34 +++++++++++++------------ 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 9a99833..8acbe99 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -89,25 +89,27 @@ class Search extends React.Component { } = this.state; return ( -
-
-
- } +
+
+
+
+ } + /> +
+ dispatch(inputChange(event.target.value))} + onKeyDown={this.onKeyDown} + value={searchText} + placeholder="Zoeken" + onFocus={this.onFocus} + onBlur={this.onBlur} />
- dispatch(inputChange(event.target.value))} - onKeyDown={this.onKeyDown} - value={searchText} - placeholder="Zoeken" - onFocus={this.onFocus} - onBlur={this.onBlur} - /> +
-
); } -- cgit v1.1 From 19534b4770b4f4097b02f5fa021a24822b12d907 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Fri, 26 Jan 2018 20:30:34 +0100 Subject: Add week selector --- .../react/components/container/WeekSelector.js | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/client/react/components/container/WeekSelector.js (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/WeekSelector.js b/src/client/react/components/container/WeekSelector.js new file mode 100644 index 0000000..9f308f0 --- /dev/null +++ b/src/client/react/components/container/WeekSelector.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import momentPropTypes from 'react-moment-proptypes'; +import queryString from 'query-string'; +import { withRouter } from 'react-router-dom'; + +const WeekSelector = ({ urlWeek, location, history }) => { + const updateWeek = (change) => { + const newWeek = moment().week(urlWeek.week() + change); + const isCurrentWeek = moment().week() === newWeek.week(); + history.push(`${location.pathname}?${queryString.stringify({ week: isCurrentWeek ? undefined : newWeek.week() })}`); + }; + + return ( +
+ + Week {urlWeek.week()} + +
+ ); +}; + +WeekSelector.propTypes = { + urlWeek: momentPropTypes.momentObj.isRequired, + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + }).isRequired, + location: PropTypes.shape({ + pathname: PropTypes.string.isRequired, + }).isRequired, +}; + +export default withRouter(WeekSelector); -- cgit v1.1 From 0c99c0b4d84f53675cc3d42fa518879789cc86b0 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Fri, 26 Jan 2018 20:34:35 +0100 Subject: Make updateWeek sub 80 chars --- src/client/react/components/container/WeekSelector.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/WeekSelector.js b/src/client/react/components/container/WeekSelector.js index 9f308f0..c9174ca 100644 --- a/src/client/react/components/container/WeekSelector.js +++ b/src/client/react/components/container/WeekSelector.js @@ -9,7 +9,11 @@ const WeekSelector = ({ urlWeek, location, history }) => { const updateWeek = (change) => { const newWeek = moment().week(urlWeek.week() + change); const isCurrentWeek = moment().week() === newWeek.week(); - history.push(`${location.pathname}?${queryString.stringify({ week: isCurrentWeek ? undefined : newWeek.week() })}`); + + const query = queryString.stringify({ + week: isCurrentWeek ? undefined : newWeek.week(), + }); + history.push(`${location.pathname}?${query}`); }; return ( -- cgit v1.1 From 8670ada517bc8beb69d152c82f282322b9ea8d64 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 15:43:11 +0100 Subject: Implement week selector in the view --- src/client/react/components/container/View.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index 9bac66f..be550cd 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -16,24 +16,26 @@ function cleanMeetingpointHTML(htmlStr) { class View extends React.Component { componentDidMount() { - if (!this.loadingFinished(this.props.user)) { - this.props.dispatch(fetchSchedule(this.props.user)); + if (!this.loadingFinished(this.props.user, this.props.week)) { + this.props.dispatch(fetchSchedule(this.props.user, this.props.week)); } } componentWillReceiveProps(nextProps) { - if (nextProps.user !== this.props.user && !this.loadingFinished(nextProps.user)) { - this.props.dispatch(fetchSchedule(nextProps.user)); + if ((nextProps.user !== this.props.user || nextProps.week !== this.props.week) + && !this.loadingFinished(nextProps.user, nextProps.week)) { + this.props.dispatch(fetchSchedule(nextProps.user, nextProps.week)); } } - loadingFinished(user) { + loadingFinished(user, week) { return this.props.schedules.hasOwnProperty(user) && - this.props.schedules[user].state === 'finished'; + this.props.schedules[user].hasOwnProperty(week) && + this.props.schedules[user][week].state === 'finished'; } render() { - if (!this.loadingFinished(this.props.user)) { + if (!this.loadingFinished(this.props.user, this.props.week)) { return (
Loading... @@ -41,7 +43,7 @@ class View extends React.Component { ); } - const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user].htmlStr); + const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user][this.props.week].htmlStr); return ( // eslint-disable-next-line react/no-danger -- cgit v1.1 From 3b98d4c4f13424c89a10580065075998d37ae857 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 16:06:56 +0100 Subject: Improve week logic --- src/client/react/components/container/WeekSelector.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/WeekSelector.js b/src/client/react/components/container/WeekSelector.js index c9174ca..eef8d8d 100644 --- a/src/client/react/components/container/WeekSelector.js +++ b/src/client/react/components/container/WeekSelector.js @@ -1,17 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; -import momentPropTypes from 'react-moment-proptypes'; import queryString from 'query-string'; import { withRouter } from 'react-router-dom'; +import purifyWeek from '../../lib/purifyWeek'; + const WeekSelector = ({ urlWeek, location, history }) => { const updateWeek = (change) => { - const newWeek = moment().week(urlWeek.week() + change); - const isCurrentWeek = moment().week() === newWeek.week(); + const newWeek = purifyWeek(urlWeek + change); + const isCurrentWeek = moment().week() === newWeek; const query = queryString.stringify({ - week: isCurrentWeek ? undefined : newWeek.week(), + week: isCurrentWeek ? undefined : newWeek, }); history.push(`${location.pathname}?${query}`); }; @@ -19,14 +20,14 @@ const WeekSelector = ({ urlWeek, location, history }) => { return (
- Week {urlWeek.week()} + Week {urlWeek}
); }; WeekSelector.propTypes = { - urlWeek: momentPropTypes.momentObj.isRequired, + urlWeek: PropTypes.number.isRequired, history: PropTypes.shape({ push: PropTypes.func.isRequired, }).isRequired, -- cgit v1.1 From 9f438225db07b7214e4a41d133634309cba80073 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 16:10:10 +0100 Subject: Fix View.js prop types --- src/client/react/components/container/View.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index be550cd..7539609 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -54,11 +54,12 @@ class View extends React.Component { View.propTypes = { user: PropTypes.string, + week: PropTypes.number.isRequired, dispatch: PropTypes.func.isRequired, - schedules: PropTypes.objectOf(PropTypes.shape({ + schedules: PropTypes.objectOf(PropTypes.objectOf(PropTypes.shape({ state: PropTypes.string.isRequired, htmlStr: PropTypes.string, - })).isRequired, + }))).isRequired, }; View.defaultProps = { -- cgit v1.1 From 7dd214d57c7dab9626abef1516a862f46c1e02bd Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 19:11:00 +0100 Subject: Refactor View.js --- src/client/react/components/container/View.js | 68 ++++++++++++--------------- 1 file changed, 29 insertions(+), 39 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index 7539609..2823fe9 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -5,55 +5,49 @@ 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'], - }); -} +import extractSchedule from '../../lib/extractSchedule'; class View extends React.Component { - componentDidMount() { - if (!this.loadingFinished(this.props.user, this.props.week)) { - this.props.dispatch(fetchSchedule(this.props.user, this.props.week)); - } - } - - componentWillReceiveProps(nextProps) { - if ((nextProps.user !== this.props.user || nextProps.week !== this.props.week) - && !this.loadingFinished(nextProps.user, nextProps.week)) { - this.props.dispatch(fetchSchedule(nextProps.user, nextProps.week)); - } - } - - loadingFinished(user, week) { - return this.props.schedules.hasOwnProperty(user) && - this.props.schedules[user].hasOwnProperty(week) && - this.props.schedules[user][week].state === 'finished'; + renderLoading() { + return ( +
+ Loading... +
+ ); } - render() { - if (!this.loadingFinished(this.props.user, this.props.week)) { - return ( -
- Loading... -
- ); - } + renderSchedule(htmlStr) { + const DOMPurify = createDOMPurify(window); - const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user][this.props.week].htmlStr); + const cleanHTML = DOMPurify.sanitize(htmlStr, { + ADD_ATTR: ['rules'], + }); return ( // eslint-disable-next-line react/no-danger
); } + + render() { + const schedule = extractSchedule(this.props.schedules, this.props.user, this.props.week); + + switch (schedule.state) { + case 'NOT_REQUESTED': + this.props.dispatch(fetchSchedule(this.props.user, this.props.week)); + return this.renderLoading(); + case 'FETCHING': + return this.renderLoading(); + case 'FINISHED': + return this.renderSchedule(schedule.htmlStr); + default: + throw new Error(`${schedule.state} is not a valid schedule state.`); + } + } } View.propTypes = { - user: PropTypes.string, + user: PropTypes.string.isRequired, week: PropTypes.number.isRequired, dispatch: PropTypes.func.isRequired, schedules: PropTypes.objectOf(PropTypes.objectOf(PropTypes.shape({ @@ -62,10 +56,6 @@ View.propTypes = { }))).isRequired, }; -View.defaultProps = { - user: null, -}; - const mapStateToProps = state => ({ schedules: state.view.schedules, }); -- cgit v1.1 From 9e539c650b40ea76f9c7d00d9b28b33905d1b1d6 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 19:25:17 +0100 Subject: Make a stateless component --- src/client/react/components/container/View.js | 73 ++++++++++++++------------- 1 file changed, 37 insertions(+), 36 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index 2823fe9..7ac5d3e 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -7,53 +7,54 @@ import { withRouter } from 'react-router-dom'; import { fetchSchedule } from '../../actions/view'; import extractSchedule from '../../lib/extractSchedule'; -class View extends React.Component { - renderLoading() { - return ( -
- Loading... -
- ); - } +const Loading = () =>
Loading...
; - renderSchedule(htmlStr) { - const DOMPurify = createDOMPurify(window); +const Schedule = ({ htmlStr }) => { + const DOMPurify = createDOMPurify(window); - const cleanHTML = DOMPurify.sanitize(htmlStr, { - ADD_ATTR: ['rules'], - }); + const cleanHTML = DOMPurify.sanitize(htmlStr, { + ADD_ATTR: ['rules'], + }); - return ( - // eslint-disable-next-line react/no-danger -
- ); - } + return ( + // eslint-disable-next-line react/no-danger +
+ ); +}; - render() { - const schedule = extractSchedule(this.props.schedules, this.props.user, this.props.week); - - switch (schedule.state) { - case 'NOT_REQUESTED': - this.props.dispatch(fetchSchedule(this.props.user, this.props.week)); - return this.renderLoading(); - case 'FETCHING': - return this.renderLoading(); - case 'FINISHED': - return this.renderSchedule(schedule.htmlStr); - default: - throw new Error(`${schedule.state} is not a valid schedule state.`); - } +Schedule.propTypes = { + htmlStr: PropTypes.string.isRequired, +}; + +const View = ({ + schedules, + user, + week, + dispatch, +}) => { + const schedule = extractSchedule(schedules, user, week); + + switch (schedule.state) { + case 'NOT_REQUESTED': + dispatch(fetchSchedule(user, week)); + return ; + case 'FETCHING': + return ; + case 'FINISHED': + return ; + default: + throw new Error(`${schedule.state} is not a valid schedule state.`); } -} +}; View.propTypes = { - user: PropTypes.string.isRequired, - week: PropTypes.number.isRequired, - dispatch: PropTypes.func.isRequired, schedules: PropTypes.objectOf(PropTypes.objectOf(PropTypes.shape({ state: PropTypes.string.isRequired, htmlStr: PropTypes.string, }))).isRequired, + user: PropTypes.string.isRequired, + week: PropTypes.number.isRequired, + dispatch: PropTypes.func.isRequired, }; const mapStateToProps = state => ({ -- cgit v1.1 From dde583c2fa9b990e1d30f7292f9cf28d9310e570 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 19:59:09 +0100 Subject: Move Schedule and Loading to seperate files --- src/client/react/components/container/View.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) (limited to 'src/client/react/components/container') diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index 7ac5d3e..4f16100 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -1,30 +1,13 @@ 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'; import extractSchedule from '../../lib/extractSchedule'; -const Loading = () =>
Loading...
; - -const Schedule = ({ htmlStr }) => { - const DOMPurify = createDOMPurify(window); - - const cleanHTML = DOMPurify.sanitize(htmlStr, { - ADD_ATTR: ['rules'], - }); - - return ( - // eslint-disable-next-line react/no-danger -
- ); -}; - -Schedule.propTypes = { - htmlStr: PropTypes.string.isRequired, -}; +import Schedule from '../presentational/Schedule'; +import Loading from '../presentational/Loading'; const View = ({ schedules, -- cgit v1.1