Feature(theme): add nightmode react logic
We wait to load the user before applying the theme as we will begin aggressively caching most of the react app routes. This means we can not depend on user data to determine.
This commit is contained in:
@ -8,6 +8,7 @@ import codeStorageSaga from './code-storage-saga';
|
|||||||
import gitterSaga from './gitter-saga';
|
import gitterSaga from './gitter-saga';
|
||||||
import mouseTrapSaga from './mouse-trap-saga';
|
import mouseTrapSaga from './mouse-trap-saga';
|
||||||
import analyticsSaga from './analytics-saga';
|
import analyticsSaga from './analytics-saga';
|
||||||
|
import nightModeSaga from './night-mode-saga';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
errSaga,
|
errSaga,
|
||||||
@ -19,5 +20,6 @@ export default [
|
|||||||
codeStorageSaga,
|
codeStorageSaga,
|
||||||
gitterSaga,
|
gitterSaga,
|
||||||
mouseTrapSaga,
|
mouseTrapSaga,
|
||||||
analyticsSaga
|
analyticsSaga,
|
||||||
|
nightModeSaga
|
||||||
];
|
];
|
||||||
|
47
client/sagas/night-mode-saga.js
Normal file
47
client/sagas/night-mode-saga.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Observable } from 'rx';
|
||||||
|
import { postJSON$ } from '../../common/utils/ajax-stream';
|
||||||
|
import types from '../../common/app/redux/types';
|
||||||
|
import {
|
||||||
|
addThemeToBody,
|
||||||
|
updateTheme,
|
||||||
|
createErrorObservable
|
||||||
|
} from '../../common/app/redux/actions';
|
||||||
|
|
||||||
|
export default function nightModeSaga(
|
||||||
|
actions,
|
||||||
|
getState,
|
||||||
|
{ document: { body } }
|
||||||
|
) {
|
||||||
|
const toggleBodyClass = actions
|
||||||
|
.filter(({ type }) => types.addThemeToBody === type)
|
||||||
|
.doOnNext(({ payload: theme }) => {
|
||||||
|
if (theme === 'night') {
|
||||||
|
body.classList.add('night');
|
||||||
|
} else {
|
||||||
|
body.classList.remove('night');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(() => false);
|
||||||
|
const toggle = actions
|
||||||
|
.filter(({ type }) => types.toggleNightMode === type);
|
||||||
|
|
||||||
|
const optimistic = toggle
|
||||||
|
.flatMap(() => {
|
||||||
|
const { app: { theme } } = getState();
|
||||||
|
const newTheme = !theme || theme === 'default' ? 'night' : 'default';
|
||||||
|
return Observable.of(
|
||||||
|
updateTheme(newTheme),
|
||||||
|
addThemeToBody(newTheme)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ajax = toggle
|
||||||
|
.debounce(250)
|
||||||
|
.flatMapLatest(() => {
|
||||||
|
const { app: { theme, csrfToken: _csrf } } = getState();
|
||||||
|
return postJSON$('/update-my-theme', { _csrf, theme })
|
||||||
|
.catch(createErrorObservable);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Observable.merge(optimistic, toggleBodyClass, ajax);
|
||||||
|
}
|
@ -188,4 +188,13 @@ export const closeHelpChat = createAction(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const toggleNightMode = createAction(types.toggleNightMode);
|
export const toggleNightMode = createAction(
|
||||||
|
types.toggleNightMode,
|
||||||
|
// we use this function to avoid hanging onto the eventObject
|
||||||
|
// so that react can recycle it
|
||||||
|
() => null
|
||||||
|
);
|
||||||
|
// updateTheme(theme: /night|default/) => Action
|
||||||
|
export const updateTheme = createAction(types.updateTheme);
|
||||||
|
// addThemeToBody(theme: /night|default/) => Action
|
||||||
|
export const addThemeToBody = createAction(types.addThemeToBody);
|
||||||
|
@ -5,11 +5,12 @@ import {
|
|||||||
updateThisUser,
|
updateThisUser,
|
||||||
updateCompletedChallenges,
|
updateCompletedChallenges,
|
||||||
createErrorObservable,
|
createErrorObservable,
|
||||||
showSignIn
|
showSignIn,
|
||||||
|
updateTheme,
|
||||||
|
addThemeToBody
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
|
||||||
const { fetchUser } = types;
|
const { fetchUser } = types;
|
||||||
|
|
||||||
export default function getUserSaga(action$, getState, { services }) {
|
export default function getUserSaga(action$, getState, { services }) {
|
||||||
return action$
|
return action$
|
||||||
.filter(action => action.type === fetchUser)
|
.filter(action => action.type === fetchUser)
|
||||||
@ -19,10 +20,14 @@ export default function getUserSaga(action$, getState, { services }) {
|
|||||||
if (!entities || !result) {
|
if (!entities || !result) {
|
||||||
return Observable.just(showSignIn());
|
return Observable.just(showSignIn());
|
||||||
}
|
}
|
||||||
|
const user = entities.user[result];
|
||||||
|
const isNightMode = user.theme === 'night';
|
||||||
return Observable.of(
|
return Observable.of(
|
||||||
addUser(entities),
|
addUser(entities),
|
||||||
|
updateCompletedChallenges(result),
|
||||||
updateThisUser(result),
|
updateThisUser(result),
|
||||||
updateCompletedChallenges(result)
|
isNightMode ? updateTheme(user.theme) : null,
|
||||||
|
isNightMode ? addThemeToBody(user.theme) : null
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(createErrorObservable);
|
.catch(createErrorObservable);
|
||||||
|
@ -10,7 +10,8 @@ const initialState = {
|
|||||||
windowHeight: 0,
|
windowHeight: 0,
|
||||||
navHeight: 0,
|
navHeight: 0,
|
||||||
isMainChatOpen: false,
|
isMainChatOpen: false,
|
||||||
isHelpChatOpen: false
|
isHelpChatOpen: false,
|
||||||
|
theme: 'default'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
@ -29,6 +30,10 @@ export default handleActions(
|
|||||||
...state,
|
...state,
|
||||||
lang: payload
|
lang: payload
|
||||||
}),
|
}),
|
||||||
|
[types.updateTheme]: (state, { payload = 'default' }) => ({
|
||||||
|
...state,
|
||||||
|
theme: payload
|
||||||
|
}),
|
||||||
[types.showSignIn]: state => ({
|
[types.showSignIn]: state => ({
|
||||||
...state,
|
...state,
|
||||||
shouldShowSignIn: true
|
shouldShowSignIn: true
|
||||||
|
@ -18,7 +18,6 @@ export default createTypes([
|
|||||||
'updateMyCurrentChallenge',
|
'updateMyCurrentChallenge',
|
||||||
|
|
||||||
'handleError',
|
'handleError',
|
||||||
'toggleNightMode',
|
|
||||||
// used to hit the server
|
// used to hit the server
|
||||||
'hardGoTo',
|
'hardGoTo',
|
||||||
'delayedRedirect',
|
'delayedRedirect',
|
||||||
@ -44,5 +43,10 @@ export default createTypes([
|
|||||||
|
|
||||||
'openHelpChat',
|
'openHelpChat',
|
||||||
'closeHelpChat',
|
'closeHelpChat',
|
||||||
'toggleHelpChat'
|
'toggleHelpChat',
|
||||||
|
|
||||||
|
// night mode
|
||||||
|
'toggleNightMode',
|
||||||
|
'updateTheme',
|
||||||
|
'addThemeToBody'
|
||||||
], 'app');
|
], 'app');
|
||||||
|
@ -599,6 +599,7 @@ module.exports = function(User) {
|
|||||||
.toPromise();
|
.toPromise();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// deprecated. remove once live
|
||||||
User.remoteMethod(
|
User.remoteMethod(
|
||||||
'updateTheme',
|
'updateTheme',
|
||||||
{
|
{
|
||||||
|
@ -64,8 +64,26 @@ export default function settingsController(app) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateMyTheme(req, res, next) {
|
||||||
|
req.checkBody('theme', 'Theme is invalid.').isLength({ min: 4 });
|
||||||
|
const { body: { theme } } = req;
|
||||||
|
const errors = req.validationErrors(true);
|
||||||
|
if (errors) {
|
||||||
|
return res.status(403).json({ errors });
|
||||||
|
}
|
||||||
|
if (req.user.theme === theme) {
|
||||||
|
return res.json({ msg: 'Theme already set' });
|
||||||
|
}
|
||||||
|
return req.user.updateTheme('' + theme)
|
||||||
|
.then(
|
||||||
|
data => res.json(data),
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
api.post(
|
api.post(
|
||||||
'/toggle-lockdown',
|
'/toggle-lockdown',
|
||||||
|
ifNoUser401,
|
||||||
toggleUserFlag('isLocked')
|
toggleUserFlag('isLocked')
|
||||||
);
|
);
|
||||||
api.post(
|
api.post(
|
||||||
@ -99,5 +117,12 @@ export default function settingsController(app) {
|
|||||||
ifNoUser401,
|
ifNoUser401,
|
||||||
updateMyCurrentChallenge
|
updateMyCurrentChallenge
|
||||||
);
|
);
|
||||||
|
|
||||||
|
api.post(
|
||||||
|
'/update-my-theme',
|
||||||
|
ifNoUser401,
|
||||||
|
updateMyTheme
|
||||||
|
);
|
||||||
|
|
||||||
app.use(api);
|
app.use(api);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user