feat(flash): Enable flash from the api
This commit is contained in:
committed by
mrugesh mohapatra
parent
01bc66cab3
commit
3f4af667ef
@ -3,11 +3,16 @@ import PropTypes from 'prop-types';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { createStore } from './src/redux/createStore';
|
import { createStore } from './src/redux/createStore';
|
||||||
|
import AppMountNotifier from './src/components/AppMountNotifier';
|
||||||
|
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
|
|
||||||
export const wrapRootElement = ({ element }) => {
|
export const wrapRootElement = ({ element }) => {
|
||||||
return <Provider store={store}>{element}</Provider>;
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<AppMountNotifier render={() => element} />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapRootElement.propTypes = {
|
wrapRootElement.propTypes = {
|
||||||
|
@ -69,22 +69,10 @@ export default function prodErrorHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'html') {
|
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') {
|
if (typeof req.flash === 'function') {
|
||||||
req.flash(handled.type || 'danger', message);
|
req.flash(handled.type || 'danger', message);
|
||||||
}
|
}
|
||||||
return res.redirect(redirectTo);
|
return res.redirectWithFlash(redirectTo);
|
||||||
// json
|
// json
|
||||||
} else if (type === 'json') {
|
} else if (type === 'json') {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Observable } from 'rx';
|
import qs from 'query-string';
|
||||||
|
|
||||||
// add rx methods to express
|
// add rx methods to express
|
||||||
export default function() {
|
export default function() {
|
||||||
return function expressExtensions(req, res, next) {
|
return function expressExtensions(req, res, next) {
|
||||||
// express flash will overwrite render with one that will
|
res.redirectWithFlash = uri => {
|
||||||
// dump flash messages to locals on every call to render
|
const flash = req.flash();
|
||||||
// Use this when that behavior is not wanted
|
res.redirect(
|
||||||
res.renderWithoutFlash = res.render;
|
`${uri}?${qs.stringify(
|
||||||
// render to observable stream using build in render
|
{ messages: qs.stringify(flash, { arrayFormat: 'index' }) },
|
||||||
res.render$ = Observable.fromNodeCallback(res.render, res);
|
{ arrayFormat: 'index' }
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
};
|
||||||
res.sendFlash = (type, message) => {
|
res.sendFlash = (type, message) => {
|
||||||
if (type && message) {
|
if (type && message) {
|
||||||
req.flash(type, message);
|
req.flash(type, message);
|
||||||
|
30
src/components/AppMountNotifier.js
Normal file
30
src/components/AppMountNotifier.js
Normal file
@ -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);
|
@ -3,4 +3,5 @@
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ function Flash({ messages, onClose }) {
|
|||||||
key={id}
|
key={id}
|
||||||
onDismiss={createDismissHandler(onClose, id)}
|
onDismiss={createDismissHandler(onClose, id)}
|
||||||
>
|
>
|
||||||
{message}
|
<div dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
</Alert>
|
</Alert>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
28
src/redux/app-mount-saga.js
Normal file
28
src/redux/app-mount-saga.js
Normal file
@ -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)];
|
||||||
|
}
|
@ -3,6 +3,8 @@ import { createAction, handleActions } from 'redux-actions';
|
|||||||
import { createTypes, createAsyncTypes } from '../utils/createTypes';
|
import { createTypes, createAsyncTypes } from '../utils/createTypes';
|
||||||
import { createFetchUserSaga } from './fetch-user-saga';
|
import { createFetchUserSaga } from './fetch-user-saga';
|
||||||
import { createAcceptTermsSaga } from './accept-terms-saga';
|
import { createAcceptTermsSaga } from './accept-terms-saga';
|
||||||
|
import { createAppMountSaga } from './app-mount-saga';
|
||||||
|
import { createUpdateMyEmailSaga } from './update-email-saga';
|
||||||
|
|
||||||
const ns = 'app';
|
const ns = 'app';
|
||||||
|
|
||||||
@ -18,22 +20,35 @@ const initialState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const types = createTypes(
|
const types = createTypes(
|
||||||
[...createAsyncTypes('fetchUser'), ...createAsyncTypes('acceptTerms')],
|
[
|
||||||
|
'appMount',
|
||||||
|
...createAsyncTypes('fetchUser'),
|
||||||
|
...createAsyncTypes('acceptTerms'),
|
||||||
|
...createAsyncTypes('updateMyEmail')
|
||||||
|
],
|
||||||
ns
|
ns
|
||||||
);
|
);
|
||||||
|
|
||||||
export const sagas = [
|
export const sagas = [
|
||||||
|
...createAcceptTermsSaga(types),
|
||||||
|
...createAppMountSaga(types),
|
||||||
...createFetchUserSaga(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 fetchUser = createAction(types.fetchUser);
|
||||||
export const fetchUserComplete = createAction(types.fetchUserComplete);
|
export const fetchUserComplete = createAction(types.fetchUserComplete);
|
||||||
export const fetchUserError = createAction(types.fetchUserError);
|
export const fetchUserError = createAction(types.fetchUserError);
|
||||||
|
|
||||||
export const acceptTerms = createAction(types.acceptTerms);
|
export const updateMyEmail = createAction(types.updateMyEmail);
|
||||||
export const acceptTermsComplete = createAction(types.acceptTermsComplete);
|
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
|
||||||
export const acceptTermsError = createAction(types.acceptTermsError);
|
export const updateMyEmailError = createAction(types.updateMyEmailError);
|
||||||
|
|
||||||
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
|
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
|
||||||
export const userFetchStateSelector = state => state[ns].fetchState;
|
export const userFetchStateSelector = state => state[ns].fetchState;
|
||||||
|
Reference in New Issue
Block a user