From 0141d1f9f4c7ca1755e0a5da908e9d27cf7aa0e1 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 00:41:32 +0100 Subject: Add presentation search component --- .../react/components/presentational/Search.js | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/client/react/components/presentational/Search.js (limited to 'src/client/react/components') diff --git a/src/client/react/components/presentational/Search.js b/src/client/react/components/presentational/Search.js new file mode 100644 index 0000000..0dde8a6 --- /dev/null +++ b/src/client/react/components/presentational/Search.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Search = ({ onInput, results }) => ( +
+ + +
+); + +Search.propTypes = { + onInput: PropTypes.func.isRequired, + results: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string.require, + type: PropTypes.string.require, + })).isRequired, +}; + +export default Search; -- cgit v1.1 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 ++++++++++++++++++++ src/client/react/components/presentational/Search.js | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/client/react/components/container/Search.js (limited to 'src/client/react/components') 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; diff --git a/src/client/react/components/presentational/Search.js b/src/client/react/components/presentational/Search.js index 0dde8a6..ce4a09d 100644 --- a/src/client/react/components/presentational/Search.js +++ b/src/client/react/components/presentational/Search.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Search = ({ onInput, results }) => ( +const Search = ({ onType, results }) => (
    @@ -14,7 +14,7 @@ const Search = ({ onInput, results }) => ( ); Search.propTypes = { - onInput: PropTypes.func.isRequired, + onType: PropTypes.func.isRequired, results: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string.require, type: PropTypes.string.require, -- 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 + src/client/react/components/presentational/Search.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src/client/react/components') 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 => ({ diff --git a/src/client/react/components/presentational/Search.js b/src/client/react/components/presentational/Search.js index ce4a09d..f75e612 100644 --- a/src/client/react/components/presentational/Search.js +++ b/src/client/react/components/presentational/Search.js @@ -1,10 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Search = ({ onType, results }) => ( +const Search = ({ onType, value, results }) => (
      @@ -15,6 +16,7 @@ const Search = ({ onType, results }) => ( Search.propTypes = { onType: PropTypes.func.isRequired, + value: PropTypes.func.isRequired, results: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string.require, type: PropTypes.string.require, -- 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 ++--- .../react/components/presentational/Search.js | 26 ---------------------- .../react/components/presentational/Search.jsx | 26 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 src/client/react/components/presentational/Search.js create mode 100644 src/client/react/components/presentational/Search.jsx (limited to 'src/client/react/components') 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 => ({ diff --git a/src/client/react/components/presentational/Search.js b/src/client/react/components/presentational/Search.js deleted file mode 100644 index f75e612..0000000 --- a/src/client/react/components/presentational/Search.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Search = ({ onType, value, results }) => ( -
      - -
        - {results.map(result =>
      • {result.name}
      • )} -
      -
      -); - -Search.propTypes = { - onType: PropTypes.func.isRequired, - value: PropTypes.func.isRequired, - results: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.require, - type: PropTypes.string.require, - })).isRequired, -}; - -export default Search; diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx new file mode 100644 index 0000000..1e00192 --- /dev/null +++ b/src/client/react/components/presentational/Search.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Search = ({ onType, value, results }) => ( +
      + +
        + {results.map(result =>
      • {result.name}
      • )} +
      +
      +); + +Search.propTypes = { + onType: PropTypes.func.isRequired, + value: PropTypes.string.isRequired, + results: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string.require, + type: PropTypes.string.require, + })).isRequired, +}; + +export default Search; -- 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 +++--- src/client/react/components/presentational/Search.jsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/client/react/components') 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)); }, }); diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index 1e00192..a713db4 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Search = ({ onType, value, results }) => ( +const Search = ({ onInputChange, value, results }) => (
      @@ -15,7 +15,7 @@ const Search = ({ onType, value, results }) => ( ); Search.propTypes = { - onType: PropTypes.func.isRequired, + onInputChange: PropTypes.func.isRequired, value: PropTypes.string.isRequired, results: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string.require, -- 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 +++++- .../react/components/presentational/Search.jsx | 35 ++++++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) (limited to 'src/client/react/components') 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( diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index a713db4..01c6f7d 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -1,13 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import SearchIcon from 'react-icons/lib/md/search'; -const Search = ({ onInputChange, value, results }) => ( -
      - +const Search = ({ + onInputChange, + onFocus, + onBlur, + hasFocus, + value, + results, +}) => ( +
      +
      +
      + +
        {results.map(result =>
      • {result.name}
      • )}
      @@ -16,10 +30,13 @@ const Search = ({ onInputChange, value, results }) => ( 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({ - name: PropTypes.string.require, - type: PropTypes.string.require, + name: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, })).isRequired, }; -- cgit v1.1 From 7e63aa14ea4298a1511f75e875ca001d2bd61ee8 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 13:53:07 +0100 Subject: Add results --- .../react/components/presentational/Search.jsx | 30 ++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'src/client/react/components') diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index 01c6f7d..40cd3e8 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -2,6 +2,23 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; +import PersonIcon from 'react-icons/lib/md/person'; + +const userShape = { + value: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, +}; + +const Result = ({ user }) => ( +
      +
      +
      {user.value}
      +
      +); + +Result.propTypes = { + user: PropTypes.shape(userShape).isRequired, +}; const Search = ({ onInputChange, @@ -11,7 +28,7 @@ const Search = ({ value, results, }) => ( -
      +
      0 })}>
      -
        - {results.map(result =>
      • {result.name}
      • )} -
      + {results.map(user => ( + + ))}
      ); @@ -34,10 +51,7 @@ Search.propTypes = { onBlur: PropTypes.func.isRequired, hasFocus: PropTypes.bool.isRequired, value: PropTypes.string.isRequired, - results: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - })).isRequired, + results: PropTypes.arrayOf(PropTypes.shape(userShape)).isRequired, }; export default Search; -- cgit v1.1 From 29338e66b28daee52f7fe5a5cdab49140b3e5a60 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 10 Dec 2017 14:06:11 +0100 Subject: Show correct icon based on user type --- .../react/components/presentational/Search.jsx | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'src/client/react/components') diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index 40cd3e8..bdddf06 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -2,19 +2,43 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; -import PersonIcon from 'react-icons/lib/md/person'; +import StudentIcon from 'react-icons/lib/md/person'; +import RoomIcon from 'react-icons/lib/md/room'; +import ClassIcon from 'react-icons/lib/md/group'; +import TeacherIcon from 'react-icons/lib/md/account-circle'; const userShape = { value: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }; -const Result = ({ user }) => ( -
      -
      -
      {user.value}
      -
      -); +const Result = ({ user }) => { + let icon; + + switch (user.type) { + case 'c': + icon = ; + break; + case 't': + icon = ; + break; + case 's': + icon = ; + break; + case 'r': + icon = ; + break; + default: + throw new Error(`Invalid user type: ${user.type}`); + } + + return ( +
      +
      {icon}
      +
      {user.value}
      +
      + ); +}; Result.propTypes = { user: PropTypes.shape(userShape).isRequired, -- 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 + .../react/components/presentational/Search.jsx | 51 +++++++++++++--------- 2 files changed, 32 insertions(+), 20 deletions(-) (limited to 'src/client/react/components') 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 => ({ diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index bdddf06..78beb56 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -12,34 +12,37 @@ const userShape = { type: PropTypes.string.isRequired, }; -const Result = ({ user }) => { - let icon; - - switch (user.type) { +const IconFromUserType = ({ userType }) => { + switch (userType) { case 'c': - icon = ; - break; + return ; case 't': - icon = ; - break; + return ; case 's': - icon = ; - break; + return ; case 'r': - icon = ; - break; + return ; default: - throw new Error(`Invalid user type: ${user.type}`); + return ; } +}; - return ( -
      -
      {icon}
      -
      {user.value}
      -
      - ); +IconFromUserType.propTypes = { + userType: PropTypes.string, }; +IconFromUserType.defaultProps = { + userType: null, +}; + + +const Result = ({ user }) => ( +
      +
      +
      {user.value}
      +
      +); + Result.propTypes = { user: PropTypes.shape(userShape).isRequired, }; @@ -51,10 +54,12 @@ const Search = ({ hasFocus, value, results, + exactMatch, }) => (
      0 })}>
      -
      + {/* Show the icon from the exact match if there is an exact match, otherwise show the search icon. */} +
      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') 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 + src/client/react/components/presentational/Search.jsx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/react/components') 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)); diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index 78beb56..fc8ca98 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -35,7 +35,6 @@ IconFromUserType.defaultProps = { userType: null, }; - const Result = ({ user }) => (
      @@ -61,6 +60,7 @@ const Search = ({ {/* Show the icon from the exact match if there is an exact match, otherwise show the search icon. */}
      Date: Mon, 11 Dec 2017 19:36:50 +0100 Subject: Move IconFromUserType and Result to seperate file --- .../components/presentational/IconFromUserType.jsx | 37 +++++++++++++++++ .../react/components/presentational/Result.jsx | 18 ++++++++ .../react/components/presentational/Search.jsx | 48 ++++------------------ 3 files changed, 64 insertions(+), 39 deletions(-) create mode 100644 src/client/react/components/presentational/IconFromUserType.jsx create mode 100644 src/client/react/components/presentational/Result.jsx (limited to 'src/client/react/components') diff --git a/src/client/react/components/presentational/IconFromUserType.jsx b/src/client/react/components/presentational/IconFromUserType.jsx new file mode 100644 index 0000000..6bd2a21 --- /dev/null +++ b/src/client/react/components/presentational/IconFromUserType.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import StudentIcon from 'react-icons/lib/md/person'; +import RoomIcon from 'react-icons/lib/md/room'; +import ClassIcon from 'react-icons/lib/md/group'; +import TeacherIcon from 'react-icons/lib/md/account-circle'; + +const IconFromUserType = ({ userType, defaultIcon }) => { + switch (userType) { + case 'c': + return ; + case 't': + return ; + case 's': + return ; + case 'r': + return ; + default: + if (defaultIcon) { + return defaultIcon; + } + + throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); + } +}; + +IconFromUserType.propTypes = { + userType: PropTypes.string, + defaultIcon: PropTypes.react, +}; + +IconFromUserType.defaultProps = { + userType: null, + defaultIcon: null, +}; + +export default IconFromUserType; diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx new file mode 100644 index 0000000..4876493 --- /dev/null +++ b/src/client/react/components/presentational/Result.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import IconFromUserType from './IconFromUserType'; + +const Result = ({ user }) => ( +
      +
      +
      {user.value}
      +
      +); + +Result.propTypes = { + user: PropTypes.shape({ + value: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + }).isRequired, +}; diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx index fc8ca98..096cdf3 100644 --- a/src/client/react/components/presentational/Search.jsx +++ b/src/client/react/components/presentational/Search.jsx @@ -2,50 +2,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; -import StudentIcon from 'react-icons/lib/md/person'; -import RoomIcon from 'react-icons/lib/md/room'; -import ClassIcon from 'react-icons/lib/md/group'; -import TeacherIcon from 'react-icons/lib/md/account-circle'; + +import IconFromUserType from './IconFromUserType'; +import Result from './Result'; const userShape = { value: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }; -const IconFromUserType = ({ userType }) => { - switch (userType) { - case 'c': - return ; - case 't': - return ; - case 's': - return ; - case 'r': - return ; - default: - return ; - } -}; - -IconFromUserType.propTypes = { - userType: PropTypes.string, -}; - -IconFromUserType.defaultProps = { - userType: null, -}; - -const Result = ({ user }) => ( -
      -
      -
      {user.value}
      -
      -); - -Result.propTypes = { - user: PropTypes.shape(userShape).isRequired, -}; - const Search = ({ onInputChange, onFocus, @@ -58,7 +23,12 @@ const Search = ({
      0 })}>
      {/* Show the icon from the exact match if there is an exact match, otherwise show the search icon. */} -
      +
      + } + /> +
      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 ++++++++++++++++++++++ .../react/components/presentational/Search.jsx | 61 ---------------- 3 files changed, 85 insertions(+), 91 deletions(-) delete mode 100644 src/client/react/components/container/Search.js create mode 100644 src/client/react/components/container/Search.jsx delete mode 100644 src/client/react/components/presentational/Search.jsx (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/presentational/Search.jsx b/src/client/react/components/presentational/Search.jsx deleted file mode 100644 index 096cdf3..0000000 --- a/src/client/react/components/presentational/Search.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import SearchIcon from 'react-icons/lib/md/search'; - -import IconFromUserType from './IconFromUserType'; -import Result from './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, -}; - -export default 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 ++++--------- .../react/components/presentational/IconFromUserType.jsx | 2 +- src/client/react/components/presentational/Result.jsx | 2 ++ 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 src/client/react/components/container/Results.jsx (limited to 'src/client/react/components') 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), }; diff --git a/src/client/react/components/presentational/IconFromUserType.jsx b/src/client/react/components/presentational/IconFromUserType.jsx index 6bd2a21..ee0e04b 100644 --- a/src/client/react/components/presentational/IconFromUserType.jsx +++ b/src/client/react/components/presentational/IconFromUserType.jsx @@ -26,7 +26,7 @@ const IconFromUserType = ({ userType, defaultIcon }) => { IconFromUserType.propTypes = { userType: PropTypes.string, - defaultIcon: PropTypes.react, + defaultIcon: PropTypes.element, }; IconFromUserType.defaultProps = { diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx index 4876493..a4a0b8e 100644 --- a/src/client/react/components/presentational/Result.jsx +++ b/src/client/react/components/presentational/Result.jsx @@ -16,3 +16,5 @@ Result.propTypes = { type: PropTypes.string.isRequired, }).isRequired, }; + +export default Result; -- 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') 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') 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') 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') 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 +++++++++++++++++++- .../react/components/presentational/Result.jsx | 10 ++++++++-- 3 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src/client/react/components') 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} diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx index a4a0b8e..80f65d4 100644 --- a/src/client/react/components/presentational/Result.jsx +++ b/src/client/react/components/presentational/Result.jsx @@ -1,10 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import IconFromUserType from './IconFromUserType'; -const Result = ({ user }) => ( -
      +const Result = ({ user, selected }) => ( +
      {user.value}
      @@ -15,6 +20,7 @@ Result.propTypes = { value: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }).isRequired, + selected: PropTypes.bool.isRequired, }; export default Result; -- 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 +++------- .../react/components/presentational/Result.jsx | 16 +++++++--------- 3 files changed, 18 insertions(+), 28 deletions(-) (limited to 'src/client/react/components') 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, }; diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx index 80f65d4..0b9e024 100644 --- a/src/client/react/components/presentational/Result.jsx +++ b/src/client/react/components/presentational/Result.jsx @@ -1,26 +1,24 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import users from '../../users'; import IconFromUserType from './IconFromUserType'; -const Result = ({ user, selected }) => ( +const Result = ({ userId, isSelected }) => (
      -
      -
      {user.value}
      +
      +
      {users.byId[userId].value}
      ); Result.propTypes = { - user: PropTypes.shape({ - value: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - }).isRequired, - selected: PropTypes.bool.isRequired, + userId: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, }; export default Result; -- 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') 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 --------------------- .../components/presentational/IconFromUserType.js | 37 +++++++ .../components/presentational/IconFromUserType.jsx | 37 ------- .../react/components/presentational/Result.js | 24 +++++ .../react/components/presentational/Result.jsx | 24 ----- 8 files changed, 202 insertions(+), 202 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 create mode 100644 src/client/react/components/presentational/IconFromUserType.js delete mode 100644 src/client/react/components/presentational/IconFromUserType.jsx create mode 100644 src/client/react/components/presentational/Result.js delete mode 100644 src/client/react/components/presentational/Result.jsx (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/presentational/IconFromUserType.js b/src/client/react/components/presentational/IconFromUserType.js new file mode 100644 index 0000000..ee0e04b --- /dev/null +++ b/src/client/react/components/presentational/IconFromUserType.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import StudentIcon from 'react-icons/lib/md/person'; +import RoomIcon from 'react-icons/lib/md/room'; +import ClassIcon from 'react-icons/lib/md/group'; +import TeacherIcon from 'react-icons/lib/md/account-circle'; + +const IconFromUserType = ({ userType, defaultIcon }) => { + switch (userType) { + case 'c': + return ; + case 't': + return ; + case 's': + return ; + case 'r': + return ; + default: + if (defaultIcon) { + return defaultIcon; + } + + throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); + } +}; + +IconFromUserType.propTypes = { + userType: PropTypes.string, + defaultIcon: PropTypes.element, +}; + +IconFromUserType.defaultProps = { + userType: null, + defaultIcon: null, +}; + +export default IconFromUserType; diff --git a/src/client/react/components/presentational/IconFromUserType.jsx b/src/client/react/components/presentational/IconFromUserType.jsx deleted file mode 100644 index ee0e04b..0000000 --- a/src/client/react/components/presentational/IconFromUserType.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import StudentIcon from 'react-icons/lib/md/person'; -import RoomIcon from 'react-icons/lib/md/room'; -import ClassIcon from 'react-icons/lib/md/group'; -import TeacherIcon from 'react-icons/lib/md/account-circle'; - -const IconFromUserType = ({ userType, defaultIcon }) => { - switch (userType) { - case 'c': - return ; - case 't': - return ; - case 's': - return ; - case 'r': - return ; - default: - if (defaultIcon) { - return defaultIcon; - } - - throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); - } -}; - -IconFromUserType.propTypes = { - userType: PropTypes.string, - defaultIcon: PropTypes.element, -}; - -IconFromUserType.defaultProps = { - userType: null, - defaultIcon: null, -}; - -export default IconFromUserType; diff --git a/src/client/react/components/presentational/Result.js b/src/client/react/components/presentational/Result.js new file mode 100644 index 0000000..0b9e024 --- /dev/null +++ b/src/client/react/components/presentational/Result.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import users from '../../users'; + +import IconFromUserType from './IconFromUserType'; + +const Result = ({ userId, isSelected }) => ( +
      +
      +
      {users.byId[userId].value}
      +
      +); + +Result.propTypes = { + userId: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, +}; + +export default Result; diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx deleted file mode 100644 index 0b9e024..0000000 --- a/src/client/react/components/presentational/Result.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import users from '../../users'; - -import IconFromUserType from './IconFromUserType'; - -const Result = ({ userId, isSelected }) => ( -
      -
      -
      {users.byId[userId].value}
      -
      -); - -Result.propTypes = { - userId: PropTypes.string.isRequired, - isSelected: PropTypes.bool.isRequired, -}; - -export default Result; -- 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 +++++++++++++++++++++ .../components/presentational/IconFromUserType.js | 37 ------ .../components/presentational/IconFromUserType.tsx | 31 +++++ .../react/components/presentational/Result.js | 24 ---- .../react/components/presentational/Result.tsx | 18 +++ 8 files changed, 206 insertions(+), 202 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 delete mode 100644 src/client/react/components/presentational/IconFromUserType.js create mode 100644 src/client/react/components/presentational/IconFromUserType.tsx delete mode 100644 src/client/react/components/presentational/Result.js create mode 100644 src/client/react/components/presentational/Result.tsx (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/presentational/IconFromUserType.js b/src/client/react/components/presentational/IconFromUserType.js deleted file mode 100644 index ee0e04b..0000000 --- a/src/client/react/components/presentational/IconFromUserType.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import StudentIcon from 'react-icons/lib/md/person'; -import RoomIcon from 'react-icons/lib/md/room'; -import ClassIcon from 'react-icons/lib/md/group'; -import TeacherIcon from 'react-icons/lib/md/account-circle'; - -const IconFromUserType = ({ userType, defaultIcon }) => { - switch (userType) { - case 'c': - return ; - case 't': - return ; - case 's': - return ; - case 'r': - return ; - default: - if (defaultIcon) { - return defaultIcon; - } - - throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); - } -}; - -IconFromUserType.propTypes = { - userType: PropTypes.string, - defaultIcon: PropTypes.element, -}; - -IconFromUserType.defaultProps = { - userType: null, - defaultIcon: null, -}; - -export default IconFromUserType; diff --git a/src/client/react/components/presentational/IconFromUserType.tsx b/src/client/react/components/presentational/IconFromUserType.tsx new file mode 100644 index 0000000..d77ea1b --- /dev/null +++ b/src/client/react/components/presentational/IconFromUserType.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import StudentIcon = require('react-icons/lib/md/person'); +import RoomIcon = require('react-icons/lib/md/room'); +import ClassIcon = require('react-icons/lib/md/group'); +import TeacherIcon = require('react-icons/lib/md/account-circle'); + +// interface IconFromUserTypeProps { +// userType: string, +// defaultIcon?: JSX.Element, +// } + +const IconFromUserType: React.StatelessComponent<{ userType: string, defaultIcon?: JSX.Element }> = (props) => { + switch (props.userType) { + case 'c': + return ; + case 't': + return ; + case 's': + return ; + case 'r': + return ; + default: + if (props.defaultIcon) { + return props.defaultIcon; + } + + throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); + } +}; + +export default IconFromUserType; diff --git a/src/client/react/components/presentational/Result.js b/src/client/react/components/presentational/Result.js deleted file mode 100644 index 0b9e024..0000000 --- a/src/client/react/components/presentational/Result.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import users from '../../users'; - -import IconFromUserType from './IconFromUserType'; - -const Result = ({ userId, isSelected }) => ( -
      -
      -
      {users.byId[userId].value}
      -
      -); - -Result.propTypes = { - userId: PropTypes.string.isRequired, - isSelected: PropTypes.bool.isRequired, -}; - -export default Result; diff --git a/src/client/react/components/presentational/Result.tsx b/src/client/react/components/presentational/Result.tsx new file mode 100644 index 0000000..b33a365 --- /dev/null +++ b/src/client/react/components/presentational/Result.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import * as classnames from 'classnames'; +import users from '../../users'; + +import IconFromUserType from './IconFromUserType'; + +const Result: React.StatelessComponent<{ userId: string, isSelected: boolean }> = ({ userId, isSelected }) => ( +
      +
      +
      {users.byId[userId].value}
      +
      +); + +export default Result; -- 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 +++++----------------- .../components/presentational/IconFromUserType.tsx | 2 +- 2 files changed, 7 insertions(+), 22 deletions(-) (limited to 'src/client/react/components') 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)); diff --git a/src/client/react/components/presentational/IconFromUserType.tsx b/src/client/react/components/presentational/IconFromUserType.tsx index d77ea1b..83af34c 100644 --- a/src/client/react/components/presentational/IconFromUserType.tsx +++ b/src/client/react/components/presentational/IconFromUserType.tsx @@ -9,7 +9,7 @@ import TeacherIcon = require('react-icons/lib/md/account-circle'); // defaultIcon?: JSX.Element, // } -const IconFromUserType: React.StatelessComponent<{ userType: string, defaultIcon?: JSX.Element }> = (props) => { +const IconFromUserType: React.StatelessComponent<{ userType?: string, defaultIcon?: JSX.Element }> = (props) => { switch (props.userType) { case 'c': return ; -- 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 +++++++++++++++++----- .../components/presentational/IconFromUserType.tsx | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) (limited to 'src/client/react/components') 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)); diff --git a/src/client/react/components/presentational/IconFromUserType.tsx b/src/client/react/components/presentational/IconFromUserType.tsx index 83af34c..d77ea1b 100644 --- a/src/client/react/components/presentational/IconFromUserType.tsx +++ b/src/client/react/components/presentational/IconFromUserType.tsx @@ -9,7 +9,7 @@ import TeacherIcon = require('react-icons/lib/md/account-circle'); // defaultIcon?: JSX.Element, // } -const IconFromUserType: React.StatelessComponent<{ userType?: string, defaultIcon?: JSX.Element }> = (props) => { +const IconFromUserType: React.StatelessComponent<{ userType: string, defaultIcon?: JSX.Element }> = (props) => { switch (props.userType) { case 'c': return ; -- 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 --------------------- .../components/presentational/IconFromUserType.js | 37 ++++++ .../components/presentational/IconFromUserType.tsx | 31 ----- .../react/components/presentational/Result.js | 24 ++++ .../react/components/presentational/Result.tsx | 18 --- 8 files changed, 202 insertions(+), 206 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 create mode 100644 src/client/react/components/presentational/IconFromUserType.js delete mode 100644 src/client/react/components/presentational/IconFromUserType.tsx create mode 100644 src/client/react/components/presentational/Result.js delete mode 100644 src/client/react/components/presentational/Result.tsx (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/presentational/IconFromUserType.js b/src/client/react/components/presentational/IconFromUserType.js new file mode 100644 index 0000000..ee0e04b --- /dev/null +++ b/src/client/react/components/presentational/IconFromUserType.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import StudentIcon from 'react-icons/lib/md/person'; +import RoomIcon from 'react-icons/lib/md/room'; +import ClassIcon from 'react-icons/lib/md/group'; +import TeacherIcon from 'react-icons/lib/md/account-circle'; + +const IconFromUserType = ({ userType, defaultIcon }) => { + switch (userType) { + case 'c': + return ; + case 't': + return ; + case 's': + return ; + case 'r': + return ; + default: + if (defaultIcon) { + return defaultIcon; + } + + throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); + } +}; + +IconFromUserType.propTypes = { + userType: PropTypes.string, + defaultIcon: PropTypes.element, +}; + +IconFromUserType.defaultProps = { + userType: null, + defaultIcon: null, +}; + +export default IconFromUserType; diff --git a/src/client/react/components/presentational/IconFromUserType.tsx b/src/client/react/components/presentational/IconFromUserType.tsx deleted file mode 100644 index d77ea1b..0000000 --- a/src/client/react/components/presentational/IconFromUserType.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react'; -import StudentIcon = require('react-icons/lib/md/person'); -import RoomIcon = require('react-icons/lib/md/room'); -import ClassIcon = require('react-icons/lib/md/group'); -import TeacherIcon = require('react-icons/lib/md/account-circle'); - -// interface IconFromUserTypeProps { -// userType: string, -// defaultIcon?: JSX.Element, -// } - -const IconFromUserType: React.StatelessComponent<{ userType: string, defaultIcon?: JSX.Element }> = (props) => { - switch (props.userType) { - case 'c': - return ; - case 't': - return ; - case 's': - return ; - case 'r': - return ; - default: - if (props.defaultIcon) { - return props.defaultIcon; - } - - throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.'); - } -}; - -export default IconFromUserType; diff --git a/src/client/react/components/presentational/Result.js b/src/client/react/components/presentational/Result.js new file mode 100644 index 0000000..0b9e024 --- /dev/null +++ b/src/client/react/components/presentational/Result.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import users from '../../users'; + +import IconFromUserType from './IconFromUserType'; + +const Result = ({ userId, isSelected }) => ( +
      +
      +
      {users.byId[userId].value}
      +
      +); + +Result.propTypes = { + userId: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, +}; + +export default Result; diff --git a/src/client/react/components/presentational/Result.tsx b/src/client/react/components/presentational/Result.tsx deleted file mode 100644 index b33a365..0000000 --- a/src/client/react/components/presentational/Result.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import * as classnames from 'classnames'; -import users from '../../users'; - -import IconFromUserType from './IconFromUserType'; - -const Result: React.StatelessComponent<{ userId: string, isSelected: boolean }> = ({ userId, isSelected }) => ( -
      -
      -
      {users.byId[userId].value}
      -
      -); - -export default Result; -- 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') 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') 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 928edee90f4a35eea20d581e093b002be04e9b47 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sat, 6 Jan 2018 13:16:31 +0100 Subject: Move LandingPage.js to component/page/Index.js --- src/client/react/components/page/Index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/client/react/components/page/Index.js (limited to 'src/client/react/components') diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js new file mode 100644 index 0000000..dcc521d --- /dev/null +++ b/src/client/react/components/page/Index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Search from '../container/Search'; + +const App = ({ location }) => ( +
      + +
      +); + +App.propTypes = { + // eslint-disable-next-line react/forbid-prop-types + location: PropTypes.object.isRequired, +}; + +export default App; -- 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 ++++++-- src/client/react/components/page/Index.js | 10 ++------ src/client/react/components/page/User.js | 31 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 src/client/react/components/page/User.js (limited to 'src/client/react/components') 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)); diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js index dcc521d..a91d7a9 100644 --- a/src/client/react/components/page/Index.js +++ b/src/client/react/components/page/Index.js @@ -1,16 +1,10 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Search from '../container/Search'; -const App = ({ location }) => ( +const App = () => (
      - +
      ); -App.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - location: PropTypes.object.isRequired, -}; - export default App; diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js new file mode 100644 index 0000000..2ad65a6 --- /dev/null +++ b/src/client/react/components/page/User.js @@ -0,0 +1,31 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Redirect } from 'react-router-dom'; +import Search from '../container/Search'; +import users from '../../users'; + +const App = ({ match }) => { + const user = `${match.params.type}/${match.params.value}`; + + if (!users.allIds.includes(user)) { + // Invalid user, redirect to index. + return ; + } + + return ( +
      + +
      + ); +}; + +App.propTypes = { + match: PropTypes.shape({ + params: PropTypes.shape({ + type: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, +}; + +export default App; -- 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 +++++++++++++++++++++++++ src/client/react/components/page/User.js | 2 + 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/client/react/components/container/View.js (limited to 'src/client/react/components') 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 (
      +
      ); }; -- cgit v1.1 From 2232877ed5b3c0b60789940d2a367726ee8919c5 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 17 Jan 2018 16:45:17 +0100 Subject: Add some basic styling --- src/client/react/components/page/Index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/react/components') diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js index a91d7a9..9b8dbd7 100644 --- a/src/client/react/components/page/Index.js +++ b/src/client/react/components/page/Index.js @@ -2,7 +2,8 @@ import React from 'react'; import Search from '../container/Search'; const App = () => ( -
      +
      + Metis
      ); -- 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 ++++++++++++++++++++++++ src/client/react/components/page/Index.js | 8 ++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/client/react/components/container/HelpBox.js (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js index 9b8dbd7..c2994c9 100644 --- a/src/client/react/components/page/Index.js +++ b/src/client/react/components/page/Index.js @@ -1,10 +1,14 @@ import React from 'react'; import Search from '../container/Search'; +import HelpBox from '../container/HelpBox'; const App = () => (
      - Metis - +
      + Metis + + +
      ); -- 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') 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 +++++++++++++------------ src/client/react/components/page/User.js | 8 ++++-- 2 files changed, 24 insertions(+), 18 deletions(-) (limited to 'src/client/react/components') 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} - /> +
      -
      ); } diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js index ea8cd10..ff304c0 100644 --- a/src/client/react/components/page/User.js +++ b/src/client/react/components/page/User.js @@ -14,8 +14,12 @@ const App = ({ match }) => { } return ( -
      - +
      +
      +
      + +
      +
      ); -- 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 ++++++++++++++++++++++ src/client/react/components/page/User.js | 11 ++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/client/react/components/container/WeekSelector.js (limited to 'src/client/react/components') 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); diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js index ff304c0..980070f 100644 --- a/src/client/react/components/page/User.js +++ b/src/client/react/components/page/User.js @@ -1,12 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Redirect } from 'react-router-dom'; +import queryString from 'query-string'; +import moment from 'moment'; import Search from '../container/Search'; import View from '../container/View'; import users from '../../users'; +import WeekSelector from '../container/WeekSelector'; -const App = ({ match }) => { +const App = ({ match, location }) => { const user = `${match.params.type}/${match.params.value}`; + const weekStr = queryString.parse(location.search).week; + const week = weekStr ? moment().week(weekStr) : moment(); if (!users.allIds.includes(user)) { // Invalid user, redirect to index. @@ -18,6 +23,7 @@ const App = ({ match }) => {
      +
      @@ -32,6 +38,9 @@ App.propTypes = { value: PropTypes.string.isRequired, }).isRequired, }).isRequired, + location: PropTypes.shape({ + search: PropTypes.string.isRequired, + }).isRequired, }; export default App; -- 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') 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 ++++++++++-------- src/client/react/components/page/User.js | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src/client/react/components') 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 diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js index 980070f..72e92c2 100644 --- a/src/client/react/components/page/User.js +++ b/src/client/react/components/page/User.js @@ -26,7 +26,8 @@ const App = ({ match, location }) => {
      - + {/* The View object just wants the week number. */} +
      ); }; -- 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 +++++++------ src/client/react/components/page/User.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src/client/react/components') 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, diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js index 72e92c2..10d608d 100644 --- a/src/client/react/components/page/User.js +++ b/src/client/react/components/page/User.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { Redirect } from 'react-router-dom'; import queryString from 'query-string'; import moment from 'moment'; +import purifyWeek from '../../lib/purifyWeek'; import Search from '../container/Search'; import View from '../container/View'; import users from '../../users'; @@ -11,7 +12,7 @@ import WeekSelector from '../container/WeekSelector'; const App = ({ match, location }) => { const user = `${match.params.type}/${match.params.value}`; const weekStr = queryString.parse(location.search).week; - const week = weekStr ? moment().week(weekStr) : moment(); + const week = purifyWeek(weekStr ? parseInt(weekStr, 10) : moment().week()); if (!users.allIds.includes(user)) { // Invalid user, redirect to index. @@ -26,8 +27,7 @@ const App = ({ match, location }) => {
      - {/* The View object just wants the week number. */} - +
      ); }; -- 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') 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') 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') 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 ++------------------- .../react/components/presentational/Loading.js | 5 +++++ .../react/components/presentational/Schedule.js | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 src/client/react/components/presentational/Loading.js create mode 100644 src/client/react/components/presentational/Schedule.js (limited to 'src/client/react/components') 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, diff --git a/src/client/react/components/presentational/Loading.js b/src/client/react/components/presentational/Loading.js new file mode 100644 index 0000000..84eaac7 --- /dev/null +++ b/src/client/react/components/presentational/Loading.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const Loading = () =>
      Loading...
      ; + +export default Loading; diff --git a/src/client/react/components/presentational/Schedule.js b/src/client/react/components/presentational/Schedule.js new file mode 100644 index 0000000..256c1b4 --- /dev/null +++ b/src/client/react/components/presentational/Schedule.js @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import createDOMPurify from 'dompurify'; + +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, +}; + +export default Schedule; -- cgit v1.1 From 9a9edd1865d619caada787231c8bb34be25af3af Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 28 Jan 2018 20:00:44 +0100 Subject: Give IndexPage and UserPage the correct name --- src/client/react/components/page/Index.js | 4 ++-- src/client/react/components/page/User.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/client/react/components') diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js index c2994c9..e5e47c5 100644 --- a/src/client/react/components/page/Index.js +++ b/src/client/react/components/page/Index.js @@ -2,7 +2,7 @@ import React from 'react'; import Search from '../container/Search'; import HelpBox from '../container/HelpBox'; -const App = () => ( +const IndexPage = () => (
      Metis @@ -12,4 +12,4 @@ const App = () => (
      ); -export default App; +export default IndexPage; diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js index 10d608d..215a6e0 100644 --- a/src/client/react/components/page/User.js +++ b/src/client/react/components/page/User.js @@ -9,7 +9,7 @@ import View from '../container/View'; import users from '../../users'; import WeekSelector from '../container/WeekSelector'; -const App = ({ match, location }) => { +const UserPage = ({ match, location }) => { const user = `${match.params.type}/${match.params.value}`; const weekStr = queryString.parse(location.search).week; const week = purifyWeek(weekStr ? parseInt(weekStr, 10) : moment().week()); @@ -32,7 +32,7 @@ const App = ({ match, location }) => { ); }; -App.propTypes = { +UserPage.propTypes = { match: PropTypes.shape({ params: PropTypes.shape({ type: PropTypes.string.isRequired, @@ -44,4 +44,4 @@ App.propTypes = { }).isRequired, }; -export default App; +export default UserPage; -- cgit v1.1