aboutsummaryrefslogtreecommitdiff
path: root/src/client/react
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/react')
-rw-r--r--src/client/react/LandingPage.js (renamed from src/client/react/LandingPage.tsx)2
-rw-r--r--src/client/react/actions/search.js14
-rw-r--r--src/client/react/actions/search.ts24
-rw-r--r--src/client/react/components/container/Results.js35
-rw-r--r--src/client/react/components/container/Results.tsx26
-rw-r--r--src/client/react/components/container/Search.js (renamed from src/client/react/components/container/Search.tsx)69
-rw-r--r--src/client/react/components/presentational/IconFromUserType.js37
-rw-r--r--src/client/react/components/presentational/IconFromUserType.tsx31
-rw-r--r--src/client/react/components/presentational/Result.js (renamed from src/client/react/components/presentational/Result.tsx)12
-rw-r--r--src/client/react/index.js (renamed from src/client/react/index.tsx)8
-rw-r--r--src/client/react/reducers.js8
-rw-r--r--src/client/react/reducers.ts12
-rw-r--r--src/client/react/reducers/search.js (renamed from src/client/react/reducers/search.ts)23
-rw-r--r--src/client/react/reducers/search.test.js (renamed from src/client/react/reducers/search.test.ts)2
-rw-r--r--src/client/react/users.js (renamed from src/client/react/users.ts)35
15 files changed, 144 insertions, 194 deletions
diff --git a/src/client/react/LandingPage.tsx b/src/client/react/LandingPage.js
index f8bb58c..d79826e 100644
--- a/src/client/react/LandingPage.tsx
+++ b/src/client/react/LandingPage.js
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React from 'react';
import Search from './components/container/Search';
const App = () => (
diff --git a/src/client/react/actions/search.js b/src/client/react/actions/search.js
new file mode 100644
index 0000000..1b6847d
--- /dev/null
+++ b/src/client/react/actions/search.js
@@ -0,0 +1,14 @@
+export const inputChange = typedValue => ({
+ type: 'SEARCH/INPUT_CHANGE',
+ typedValue,
+});
+
+/**
+ * Change the selected result.
+ * @param {+1/-1} relativeChange usually +1 or -1, the change relative to the
+ * current result.
+ */
+export const changeSelectedResult = relativeChange => ({
+ type: 'SEARCH/CHANGE_SELECTED_RESULT',
+ relativeChange,
+});
diff --git a/src/client/react/actions/search.ts b/src/client/react/actions/search.ts
deleted file mode 100644
index 45c31fb..0000000
--- a/src/client/react/actions/search.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export interface InputChangeAction {
- type: 'SEARCH/INPUT_CHANGE',
- typedValue: string,
-}
-
-export const inputChange = (typedValue: string): InputChangeAction => ({
- type: 'SEARCH/INPUT_CHANGE',
- typedValue,
-});
-
-export interface ChangeSelectedResultAction {
- type: 'SEARCH/CHANGE_SELECTED_RESULT',
- relativeChange: 1 | -1,
-}
-
-/**
- * Change the selected result.
- * @param {+1/-1} relativeChange usually +1 or -1, the change relative to the
- * current result.
- */
-export const changeSelectedResult = (relativeChange: 1 | -1): ChangeSelectedResultAction => ({
- type: 'SEARCH/CHANGE_SELECTED_RESULT',
- relativeChange,
-});
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 }) => (
+ <div
+ className={classnames('search__results', {
+ 'search__results--has-results': !isExactMatch && results.length > 0,
+ })}
+ >
+ {!isExactMatch && results.map(userId => (
+ <Result key={userId} userId={userId} isSelected={userId === selectedResult} />
+ ))}
+ </div>
+));
+
+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) => (
- <div
- className={classnames('search__results', {
- 'search__results--has-results': !props.isExactMatch && props.results.length > 0,
- })}
- >
- {!props.isExactMatch && props.results.map(userId => (
- <Result key={userId} userId={userId} isSelected={userId === props.selectedResult} />
- ))}
- </div>
-);
-
-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.tsx b/src/client/react/components/container/Search.js
index fdd6c83..e49e6a7 100644
--- a/src/client/react/components/container/Search.tsx
+++ b/src/client/react/components/container/Search.js
@@ -1,30 +1,18 @@
-import * as React from 'react';
-import { Dispatch } from 'redux';
+import React from 'react';
+import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import * as classnames from 'classnames';
+import classnames from 'classnames';
-import SearchIcon = require('react-icons/lib/md/search');
+import SearchIcon from '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<SearchStatehProps & SearchDispatchProps, any> {
- constructor(props: SearchStatehProps & SearchDispatchProps) {
+class Search extends React.Component {
+ constructor(props) {
super(props);
this.state = {
@@ -48,15 +36,15 @@ class Search extends React.Component<SearchStatehProps & SearchDispatchProps, an
});
}
- onKeyDown(event: React.KeyboardEvent<any>) {
+ onKeyDown(event) {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
switch (event.key) {
case 'ArrowUp':
- this.props.changeSelectedResult(-1);
+ this.props.dispatch(changeSelectedResult(-1));
break;
case 'ArrowDown':
- this.props.changeSelectedResult(+1);
+ this.props.dispatch(changeSelectedResult(+1));
break;
default:
throw new Error('This should never happen... pls?');
@@ -68,7 +56,7 @@ class Search extends React.Component<SearchStatehProps & SearchDispatchProps, an
const {
selectedResult,
isExactMatch,
- inputChange,
+ dispatch,
} = this.props;
const {
@@ -86,7 +74,7 @@ class Search extends React.Component<SearchStatehProps & SearchDispatchProps, an
</div>
<input
id="search__input"
- onChange={event => inputChange(event.target.value)}
+ onChange={event => dispatch(inputChange(event.target.value))}
onKeyDown={this.onKeyDown}
placeholder="Zoeken"
onFocus={this.onFocus}
@@ -99,33 +87,20 @@ class Search extends React.Component<SearchStatehProps & SearchDispatchProps, an
}
}
-// Search.propTypes = {
-// selectedResult: PropTypes.string,
-// isExactMatch: PropTypes.bool.isRequired,
-// dispatch: PropTypes.func.isRequired,
-// };
+Search.propTypes = {
+ selectedResult: PropTypes.string,
+ isExactMatch: PropTypes.bool.isRequired,
+ dispatch: PropTypes.func.isRequired,
+};
-// Search.defaultProps = {
-// selectedResult: null,
-// };
+Search.defaultProps = {
+ selectedResult: null,
+};
-const mapStateToProps = (state: State):SearchStatehProps => ({
+const mapStateToProps = state => ({
+ results: state.search.results,
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);
+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 <ClassIcon />;
+ case 't':
+ return <TeacherIcon />;
+ case 's':
+ return <StudentIcon />;
+ case 'r':
+ return <RoomIcon />;
+ 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 <ClassIcon />;
- case 't':
- return <TeacherIcon />;
- case 's':
- return <StudentIcon />;
- case 'r':
- return <RoomIcon />;
- 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.tsx b/src/client/react/components/presentational/Result.js
index b33a365..0b9e024 100644
--- a/src/client/react/components/presentational/Result.tsx
+++ b/src/client/react/components/presentational/Result.js
@@ -1,10 +1,11 @@
-import * as React from 'react';
-import * as classnames from 'classnames';
+import React from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
import users from '../../users';
import IconFromUserType from './IconFromUserType';
-const Result: React.StatelessComponent<{ userId: string, isSelected: boolean }> = ({ userId, isSelected }) => (
+const Result = ({ userId, isSelected }) => (
<div
className={classnames('search__result', {
'search__result--selected': isSelected,
@@ -15,4 +16,9 @@ const Result: React.StatelessComponent<{ userId: string, isSelected: boolean }>
</div>
);
+Result.propTypes = {
+ userId: PropTypes.string.isRequired,
+ isSelected: PropTypes.bool.isRequired,
+};
+
export default Result;
diff --git a/src/client/react/index.tsx b/src/client/react/index.js
index f0c3226..5279bf4 100644
--- a/src/client/react/index.tsx
+++ b/src/client/react/index.js
@@ -1,5 +1,5 @@
-import * as React from 'react';
-import * as ReactDOM from 'react-dom';
+import React from 'react';
+import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { createStore, applyMiddleware, compose } from 'redux';
@@ -9,10 +9,10 @@ import reducer from './reducers';
import LandingPage from './LandingPage';
// eslint-disable-next-line no-underscore-dangle
-// const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
- compose(applyMiddleware(logger, thunk)),
+ composeEnhancers(applyMiddleware(logger, thunk)),
);
ReactDOM.render(
diff --git a/src/client/react/reducers.js b/src/client/react/reducers.js
new file mode 100644
index 0000000..9fdf2c4
--- /dev/null
+++ b/src/client/react/reducers.js
@@ -0,0 +1,8 @@
+import { combineReducers } from 'redux';
+import search from './reducers/search';
+
+const rootReducer = combineReducers({
+ search,
+});
+
+export default rootReducer;
diff --git a/src/client/react/reducers.ts b/src/client/react/reducers.ts
deleted file mode 100644
index 254fe76..0000000
--- a/src/client/react/reducers.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { combineReducers } from 'redux';
-import search, { State as SearchState } from './reducers/search';
-
-export interface State {
- search: SearchState,
-}
-
-const rootReducer = combineReducers<State>({
- search,
-});
-
-export default rootReducer;
diff --git a/src/client/react/reducers/search.ts b/src/client/react/reducers/search.js
index 658d3ca..2a7e7a5 100644
--- a/src/client/react/reducers/search.ts
+++ b/src/client/react/reducers/search.js
@@ -1,16 +1,7 @@
-import * as fuzzy from 'fuzzy';
-import users, { User } from '../users';
-import { InputChangeAction, ChangeSelectedResultAction } from '../actions/search';
-
-export interface State {
- results: string[],
- selectedResult: string | null,
- isExactMatch: boolean,
-};
-
-export type Action = InputChangeAction | ChangeSelectedResultAction;
+import fuzzy from 'fuzzy';
+import users from '../users';
-const DEFAULT_STATE: State = {
+const DEFAULT_STATE = {
results: [
's/18562',
],
@@ -18,22 +9,22 @@ const DEFAULT_STATE: State = {
isExactMatch: false,
};
-function getSearchResults(allUsers: User[], query: string) {
+function getSearchResults(allUsers, query) {
if (query.trim() === '') {
return [];
}
const allResults = fuzzy.filter(query, allUsers, {
- extract: (user: User) => user.value,
+ extract: user => user.value,
});
const firstResults = allResults.splice(0, 4);
- const userIds = firstResults.map((result: { original: User }) => result.original.id);
+ const userIds = firstResults.map(result => result.original.id);
return userIds;
}
-const search = (state = DEFAULT_STATE, action: Action): State => {
+const search = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case 'SEARCH/INPUT_CHANGE': {
const results = getSearchResults(users.allUsers, action.typedValue);
diff --git a/src/client/react/reducers/search.test.ts b/src/client/react/reducers/search.test.js
index 5869b81..e0ca18e 100644
--- a/src/client/react/reducers/search.test.ts
+++ b/src/client/react/reducers/search.test.js
@@ -1,4 +1,4 @@
-(<any>window).USERS = [
+window.USERS = [
{ type: 's', value: '18561' },
{ type: 's', value: '18562' },
{ type: 's', value: '18563' },
diff --git a/src/client/react/users.ts b/src/client/react/users.js
index a80a1c5..01ff093 100644
--- a/src/client/react/users.ts
+++ b/src/client/react/users.js
@@ -2,26 +2,9 @@
import { combineReducers, createStore } from 'redux';
-export interface User {
- type: string,
- value: string,
- id: string,
-}
+const getId = ({ type, value }) => `${type}/${value}`;
-type Action = {
- type: 'USERS/ADD_USER',
- user: User,
-}
-
-declare global {
- interface Window {
- USERS: User[];
- }
-}
-
-const getId = ({ type, value }: User) => `${type}/${value}`;
-
-const byId = (state = {}, action: Action) => {
+const byId = (state = {}, action) => {
switch (action.type) {
case 'USERS/ADD_USER':
return {
@@ -35,7 +18,7 @@ const byId = (state = {}, action: Action) => {
}
};
-const allIds = (state : any[] = [], action : Action) => {
+const allIds = (state = [], action) => {
switch (action.type) {
case 'USERS/ADD_USER':
return [
@@ -47,7 +30,7 @@ const allIds = (state : any[] = [], action : Action) => {
}
};
-const allUsers = (state : any[] = [], action : Action) => {
+const allUsers = (state = [], action) => {
switch (action.type) {
case 'USERS/ADD_USER':
return [
@@ -61,19 +44,13 @@ const allUsers = (state : any[] = [], action : Action) => {
}
};
-interface State {
- byId: any,
- allIds: string[],
- allUsers: User[]
-}
-
-const store = createStore(combineReducers<State>({
+const store = createStore(combineReducers({
byId,
allIds,
allUsers,
}));
-window.USERS.forEach((user) => {
+USERS.forEach((user) => {
store.dispatch({
type: 'USERS/ADD_USER',
user: {