diff --git a/gatsby-browser.js b/gatsby-browser.js
index 72217801ec..c0b032c1e0 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -3,11 +3,16 @@ import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { createStore } from './src/redux/createStore';
+import AppMountNotifier from './src/components/AppMountNotifier';
const store = createStore();
export const wrapRootElement = ({ element }) => {
- return {element};
+ return (
+
+ element} />
+
+ );
};
wrapRootElement.propTypes = {
diff --git a/server/middlewares/error-handlers.js b/server/middlewares/error-handlers.js
index 99f6ecb39f..cee4d89ac6 100644
--- a/server/middlewares/error-handlers.js
+++ b/server/middlewares/error-handlers.js
@@ -69,22 +69,10 @@ export default function prodErrorHandler() {
}
if (type === 'html') {
- if (isDev) {
- return res.render(
- 'dev-error',
- {
- ...handled,
- stack: createStackHtml(err),
- errorTitle: createErrorTitle(err),
- title: 'freeCodeCamp - Server Error',
- status
- }
- );
- }
if (typeof req.flash === 'function') {
req.flash(handled.type || 'danger', message);
}
- return res.redirect(redirectTo);
+ return res.redirectWithFlash(redirectTo);
// json
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
diff --git a/server/middlewares/express-extensions.js b/server/middlewares/express-extensions.js
index 69b4cce108..30b523ac3c 100644
--- a/server/middlewares/express-extensions.js
+++ b/server/middlewares/express-extensions.js
@@ -1,14 +1,17 @@
-import { Observable } from 'rx';
+import qs from 'query-string';
// add rx methods to express
export default function() {
return function expressExtensions(req, res, next) {
- // express flash will overwrite render with one that will
- // dump flash messages to locals on every call to render
- // Use this when that behavior is not wanted
- res.renderWithoutFlash = res.render;
- // render to observable stream using build in render
- res.render$ = Observable.fromNodeCallback(res.render, res);
+ res.redirectWithFlash = uri => {
+ const flash = req.flash();
+ res.redirect(
+ `${uri}?${qs.stringify(
+ { messages: qs.stringify(flash, { arrayFormat: 'index' }) },
+ { arrayFormat: 'index' }
+ )}`
+ );
+ };
res.sendFlash = (type, message) => {
if (type && message) {
req.flash(type, message);
diff --git a/src/components/AppMountNotifier.js b/src/components/AppMountNotifier.js
new file mode 100644
index 0000000000..5d921c577c
--- /dev/null
+++ b/src/components/AppMountNotifier.js
@@ -0,0 +1,30 @@
+import { Component } from 'react';
+import PropTypes from 'prop-types';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import { appMount } from '../redux';
+
+const mapStateToProps = () => ({});
+const mapDispatchToProps = dispatch =>
+ bindActionCreators({ appMount }, dispatch);
+
+class AppMountNotifer extends Component {
+ componentDidMount() {
+ return this.props.appMount();
+ }
+ render() {
+ return this.props.render();
+ }
+}
+
+AppMountNotifer.displayName = 'AppMountNotifier';
+AppMountNotifer.propTypes = {
+ appMount: PropTypes.func.isRequired,
+ render: PropTypes.func.isRequired
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(AppMountNotifer);
diff --git a/src/components/Flash/flash.css b/src/components/Flash/flash.css
index 5cdb916d0a..51c5e401d1 100644
--- a/src/components/Flash/flash.css
+++ b/src/components/Flash/flash.css
@@ -3,4 +3,5 @@
justify-content: space-around;
align-items: center;
flex-direction: row-reverse;
+ margin-bottom: 0px;
}
diff --git a/src/components/Flash/index.js b/src/components/Flash/index.js
index 13917ad679..5a0cb9c531 100644
--- a/src/components/Flash/index.js
+++ b/src/components/Flash/index.js
@@ -16,7 +16,7 @@ function Flash({ messages, onClose }) {
key={id}
onDismiss={createDismissHandler(onClose, id)}
>
- {message}
+
));
}
diff --git a/src/redux/app-mount-saga.js b/src/redux/app-mount-saga.js
new file mode 100644
index 0000000000..d5f2774f18
--- /dev/null
+++ b/src/redux/app-mount-saga.js
@@ -0,0 +1,28 @@
+import { put, takeEvery } from 'redux-saga/effects';
+import qs from 'query-string';
+import { createFlashMessage } from '../components/Flash/redux';
+
+function* parseMessagesSaga() {
+ const search = window.location.search.slice();
+ // TODO(Bouncey): Find a way to clear the search with causing a re-render
+ if (search) {
+ const { messages } = qs.parse(search, { arrayFormat: 'index' });
+ if (messages) {
+ const flashMap = qs.parse(messages, { arrayFormat: 'index' });
+ const flash = Object.keys(flashMap).reduce(
+ (acc, type) => [
+ ...acc,
+ ...flashMap[type].map(message => ({ type, message }))
+ ],
+ []
+ );
+ for (let i = 0; i < flash.length; i++) {
+ yield put(createFlashMessage(flash[i]));
+ }
+ }
+ }
+}
+
+export function createAppMountSaga(types) {
+ return [takeEvery(types.appMount, parseMessagesSaga)];
+}
diff --git a/src/redux/index.js b/src/redux/index.js
index 4d5c50fa99..e03baa6840 100644
--- a/src/redux/index.js
+++ b/src/redux/index.js
@@ -3,6 +3,8 @@ import { createAction, handleActions } from 'redux-actions';
import { createTypes, createAsyncTypes } from '../utils/createTypes';
import { createFetchUserSaga } from './fetch-user-saga';
import { createAcceptTermsSaga } from './accept-terms-saga';
+import { createAppMountSaga } from './app-mount-saga';
+import { createUpdateMyEmailSaga } from './update-email-saga';
const ns = 'app';
@@ -18,22 +20,35 @@ const initialState = {
};
const types = createTypes(
- [...createAsyncTypes('fetchUser'), ...createAsyncTypes('acceptTerms')],
+ [
+ 'appMount',
+ ...createAsyncTypes('fetchUser'),
+ ...createAsyncTypes('acceptTerms'),
+ ...createAsyncTypes('updateMyEmail')
+ ],
ns
);
export const sagas = [
+ ...createAcceptTermsSaga(types),
+ ...createAppMountSaga(types),
...createFetchUserSaga(types),
- ...createAcceptTermsSaga(types)
+ ...createUpdateMyEmailSaga(types)
];
+export const appMount = createAction(types.appMount);
+
+export const acceptTerms = createAction(types.acceptTerms);
+export const acceptTermsComplete = createAction(types.acceptTermsComplete);
+export const acceptTermsError = createAction(types.acceptTermsError);
+
export const fetchUser = createAction(types.fetchUser);
export const fetchUserComplete = createAction(types.fetchUserComplete);
export const fetchUserError = createAction(types.fetchUserError);
-export const acceptTerms = createAction(types.acceptTerms);
-export const acceptTermsComplete = createAction(types.acceptTermsComplete);
-export const acceptTermsError = createAction(types.acceptTermsError);
+export const updateMyEmail = createAction(types.updateMyEmail);
+export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
+export const updateMyEmailError = createAction(types.updateMyEmailError);
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
export const userFetchStateSelector = state => state[ns].fetchState;