aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json48
-rw-r--r--package.json1
-rw-r--r--src/client/react/App.js6
-rw-r--r--src/client/react/index.js12
-rw-r--r--src/client/react/lib/getHistory.js35
-rw-r--r--src/client/react/lib/url.js11
-rw-r--r--src/client/react/store/actions.js54
-rw-r--r--src/client/react/store/reducers.js1
-rw-r--r--src/client/react/store/selectors.js30
9 files changed, 110 insertions, 88 deletions
diff --git a/package-lock.json b/package-lock.json
index b3eb738..06c9573 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2802,6 +2802,15 @@
"typedarray": "0.0.6"
}
},
+ "connected-react-router": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-4.3.0.tgz",
+ "integrity": "sha512-HUXyhpA2EyOcxKftkrETWwGIsJ/PQ6yjtWMAYPfwqnjVVmMOex0RfkPpub8e+/VAU/XV+DZ62YjFeDvPo6boZw==",
+ "requires": {
+ "immutable": "3.8.2",
+ "react-router": "4.3.1"
+ }
+ },
"console-browserify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
@@ -4073,7 +4082,7 @@
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
- "color-convert": "^1.9.0"
+ "color-convert": "1.9.2"
}
}
}
@@ -4776,7 +4785,7 @@
"dev": true,
"optional": true,
"requires": {
- "minimatch": "^3.0.4"
+ "minimatch": "3.0.4"
}
},
"inflight": {
@@ -4805,7 +4814,7 @@
"bundled": true,
"dev": true,
"requires": {
- "number-is-nan": "^1.0.0"
+ "number-is-nan": "1.0.1"
}
},
"isarray": {
@@ -4819,7 +4828,7 @@
"bundled": true,
"dev": true,
"requires": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "1.1.11"
}
},
"minimist": {
@@ -4910,8 +4919,8 @@
"dev": true,
"optional": true,
"requires": {
- "ignore-walk": "^3.0.1",
- "npm-bundled": "^1.0.1"
+ "ignore-walk": "3.0.1",
+ "npm-bundled": "1.0.3"
}
},
"npmlog": {
@@ -5626,6 +5635,11 @@
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
+ "immutable": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
+ "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
+ },
"import-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
@@ -7702,11 +7716,11 @@
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
}
},
"cross-spawn": {
@@ -7715,8 +7729,8 @@
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"dev": true,
"requires": {
- "lru-cache": "^4.0.1",
- "which": "^1.2.9"
+ "lru-cache": "4.1.3",
+ "which": "1.3.1"
}
},
"form-data": {
@@ -7799,7 +7813,7 @@
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
- "ansi-regex": "^2.0.0"
+ "ansi-regex": "2.1.1"
}
},
"tough-cookie": {
@@ -8839,7 +8853,7 @@
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
- "color-convert": "^1.9.0"
+ "color-convert": "1.9.2"
}
}
}
@@ -10370,8 +10384,8 @@
"integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==",
"dev": true,
"requires": {
- "ajv": "^6.1.0",
- "ajv-keywords": "^3.1.0"
+ "ajv": "6.5.1",
+ "ajv-keywords": "3.2.0"
}
}
}
diff --git a/package.json b/package.json
index e4480e8..d46178f 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"cheerio": "^0.22.0",
"classnames": "^2.2.5",
"compression": "^1.6.2",
+ "connected-react-router": "^4.3.0",
"dompurify": "^1.0.3",
"express": "^4.16.3",
"express-handlebars": "^3.0.0",
diff --git a/src/client/react/App.js b/src/client/react/App.js
index 522b746..f4cb848 100644
--- a/src/client/react/App.js
+++ b/src/client/react/App.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
-import { Router } from 'react-router-dom';
+import { ConnectedRouter } from 'connected-react-router';
import AppRouter from './AppRouter';
@@ -17,9 +17,9 @@ export default class App extends React.Component {
return (
<Provider store={store}>
- <Router history={history}>
+ <ConnectedRouter history={history}>
<AppRouter />
- </Router>
+ </ConnectedRouter>
</Provider>
);
}
diff --git a/src/client/react/index.js b/src/client/react/index.js
index f01a361..f41c9e3 100644
--- a/src/client/react/index.js
+++ b/src/client/react/index.js
@@ -24,12 +24,12 @@ import 'whatwg-fetch';
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose as reduxCompose } from 'redux';
+import { connectRouter, routerMiddleware } from 'connected-react-router';
import thunk from 'redux-thunk';
import moment from 'moment';
import createHistory from 'history/createBrowserHistory';
-import makeGetHistory from './lib/getHistory';
import reducer from './store/reducers';
import App from './App';
import './index.scss';
@@ -43,14 +43,14 @@ const history = createHistory();
const compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || reduxCompose;
const store = createStore(
- reducer,
+ connectRouter(history)(reducer),
// Redux devtools extension
// https://github.com/zalmoxisus/redux-devtools-extension
compose(
- applyMiddleware(thunk.withExtraArgument({
- getHistory: makeGetHistory(history),
- moment,
- })),
+ applyMiddleware(
+ routerMiddleware(history),
+ thunk,
+ ),
),
);
diff --git a/src/client/react/lib/getHistory.js b/src/client/react/lib/getHistory.js
deleted file mode 100644
index daa984b..0000000
--- a/src/client/react/lib/getHistory.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- weekFromLocation,
- userFromLocation,
- makeUpdatePathname,
- makeUpdateQuery,
-} from './url';
-
-/**
- * Make a getHistory function. This function is used in index.js when creating
- * the redux store.
- * @param {history} history
- * The history object from the `history` package.
- * There may only be a single shared history object in the application, which
- * is why it's delivered from `../index.js`.
- */
-export default function makeGetHistory(history) {
- /**
- * Get a collection of helpers for common browser history interactions, and a
- * collection of precalculated values from the address bar. This function is
- * used in actions.
- */
- return function getHistory() {
- const user = userFromLocation(history.location);
- const week = weekFromLocation(history.location);
- const updatePathname = makeUpdatePathname(history);
- const updateQuery = makeUpdateQuery(history);
-
- return {
- user,
- week,
- updatePathname,
- updateQuery,
- };
- };
-}
diff --git a/src/client/react/lib/url.js b/src/client/react/lib/url.js
index 442e5eb..bb5b483 100644
--- a/src/client/react/lib/url.js
+++ b/src/client/react/lib/url.js
@@ -71,17 +71,6 @@ export function makeSetWeek(history) {
};
}
-export function makeUpdatePathname(history) {
- return function updatePathname(pathname) {
- const query = history.location.search;
- if (pathname) {
- history.push(`/${pathname}${query}`);
- } else {
- history.push(`/${query}`);
- }
- };
-}
-
export function makeUpdateQuery(history) {
return function updateQuery(newQuery) {
const query = queryString.stringify({
diff --git a/src/client/react/store/actions.js b/src/client/react/store/actions.js
index 2d2cd30..e7ef91d 100644
--- a/src/client/react/store/actions.js
+++ b/src/client/react/store/actions.js
@@ -1,19 +1,40 @@
+import { push } from 'connected-react-router';
+import queryString from 'query-string';
import users from '../users';
+import { selectUser, selectWeek, selectCurrentWeek } from './selectors';
import purifyWeek from '../lib/purifyWeek';
import withinRange from '../lib/withinRange';
-export function setUser(newUser) {
- return (dispatch, getState, { getHistory }) => {
- const { updatePathname } = getHistory();
+function updatePathname(pathname = '') {
+ return (dispatch, getState) => {
+ const query = getState().router.location.search;
+ dispatch(push(`/${pathname}${query}`));
+ };
+}
+function updateQuery(newQuery) {
+ return (dispatch, getState) => {
+ const { location } = getState().router;
+ const query = queryString.stringify({
+ ...queryString.parse(location.search),
+ ...newQuery,
+ });
+
+ dispatch(push(`${location.pathname}?${query}`));
+ };
+}
+
+export function setUser(newUser) {
+ return (dispatch) => {
dispatch({ type: 'SEARCH/RESET' });
- updatePathname(newUser);
+ dispatch(updatePathname(newUser));
};
}
export function shiftRoom(shift) {
- return (dispatch, getState, { getHistory }) => {
- const { user } = getHistory();
+ return (dispatch, getState) => {
+ const state = getState();
+ const user = selectUser(state);
const { allRoomIds } = users;
if (users.byId[user].type !== 'r') throw new Error('User must be a room');
@@ -31,27 +52,28 @@ export function shiftRoom(shift) {
}
export function setWeek(newWeek) {
- return (dispatch, getState, { getHistory, moment }) => {
- const { updateQuery } = getHistory();
+ return (dispatch, getState) => {
+ const state = getState();
+ const isCurrentWeek = selectCurrentWeek(state) === newWeek;
- const isCurrentWeek = moment().week() === newWeek;
-
- updateQuery({
+ dispatch(updateQuery({
week: isCurrentWeek ? undefined : newWeek,
- });
+ }));
};
}
export function shiftWeek(shift) {
- return (dispatch, getState, { getHistory }) => {
- const { week } = getHistory();
+ return (dispatch, getState) => {
+ const state = getState();
+ const week = selectWeek(state);
dispatch(setWeek(purifyWeek(week + shift)));
};
}
export function showRoomFinder() {
- return (dispatch, getState, { getHistory }) => {
- const { user } = getHistory();
+ return (dispatch, getState) => {
+ const state = getState();
+ const user = selectUser(state);
if (user == null || users.byId[user].type !== 'r') {
// We are not currently viewing a room, correct the situation.
diff --git a/src/client/react/store/reducers.js b/src/client/react/store/reducers.js
index 4a3576d..cd68d96 100644
--- a/src/client/react/store/reducers.js
+++ b/src/client/react/store/reducers.js
@@ -22,6 +22,7 @@ import getSearchResults from '../lib/getSearchResults';
import withinRange from '../lib/withinRange';
const DEFAULT_STATE = {
+ timestamp: Date.now(),
search: null,
isRoomFinderVisible: false,
schedules: {},
diff --git a/src/client/react/store/selectors.js b/src/client/react/store/selectors.js
new file mode 100644
index 0000000..8264cdb
--- /dev/null
+++ b/src/client/react/store/selectors.js
@@ -0,0 +1,30 @@
+import moment from 'moment';
+import queryString from 'query-string';
+import purifyWeek from '../lib/purifyWeek';
+import users from '../users';
+
+export function selectUser(store) {
+ const { location } = store.router;
+ const match = location.pathname.match(/^\/([stcr])\/([-0-9a-zA-Z]+)/);
+ if (!match) return null;
+
+ const user = `${match[1]}/${match[2]}`;
+ if (!users.allIds.includes(user)) return null;
+
+ return user;
+}
+
+export function selectCurrentWeek(store) {
+ return moment(store.now).week();
+}
+
+export function selectWeek(store) {
+ const { location } = store.router;
+ const weekStr = queryString.parse(location.search).week;
+
+ if (!weekStr) {
+ return selectCurrentWeek(store);
+ }
+
+ return purifyWeek(parseInt(weekStr, 10));
+}