mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 18:54:52 +00:00
username and settings are now persistant
This commit is contained in:
parent
6d0b703102
commit
b5e654f833
@ -17,7 +17,6 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"jquery": "3",
|
"jquery": "3",
|
||||||
"js-cookie": "^3.0.1",
|
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
|
@ -15,6 +15,10 @@ export const toggleSoundEnabled = payload => async dispatch => {
|
|||||||
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload });
|
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const togglePersistenceEnabled = payload => async dispatch => {
|
||||||
|
dispatch({ type: 'TOGGLE_PERSISTENCE_ENABLED', payload });
|
||||||
|
};
|
||||||
|
|
||||||
export const toggleNotificationEnabled = payload => async dispatch => {
|
export const toggleNotificationEnabled = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload });
|
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload });
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,9 @@ class Home extends Component {
|
|||||||
<Settings
|
<Settings
|
||||||
roomId={this.props.roomId}
|
roomId={this.props.roomId}
|
||||||
toggleSoundEnabled={this.props.toggleSoundEnabled}
|
toggleSoundEnabled={this.props.toggleSoundEnabled}
|
||||||
|
togglePersistenceEnabled={this.props.togglePersistenceEnabled}
|
||||||
soundIsEnabled={this.props.soundIsEnabled}
|
soundIsEnabled={this.props.soundIsEnabled}
|
||||||
|
persistenceIsEnabled={this.props.persistenceIsEnabled}
|
||||||
toggleNotificationEnabled={this.props.toggleNotificationEnabled}
|
toggleNotificationEnabled={this.props.toggleNotificationEnabled}
|
||||||
toggleNotificationAllowed={this.props.toggleNotificationAllowed}
|
toggleNotificationAllowed={this.props.toggleNotificationAllowed}
|
||||||
notificationIsEnabled={this.props.notificationIsEnabled}
|
notificationIsEnabled={this.props.notificationIsEnabled}
|
||||||
@ -151,7 +153,7 @@ class Home extends Component {
|
|||||||
|
|
||||||
createUser() {
|
createUser() {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
const username = nanoid();
|
const username = this.props.username || nanoid();
|
||||||
|
|
||||||
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
||||||
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
||||||
@ -238,7 +240,7 @@ Home.propTypes = {
|
|||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
publicKey: PropTypes.object.isRequired,
|
publicKey: PropTypes.object.isRequired,
|
||||||
members: PropTypes.array.isRequired,
|
members: PropTypes.array.isRequired,
|
||||||
socketId: PropTypes.object.isRequired,
|
socketId: PropTypes.string.isRequired,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
roomLocked: PropTypes.bool.isRequired,
|
roomLocked: PropTypes.bool.isRequired,
|
||||||
modalComponent: PropTypes.string,
|
modalComponent: PropTypes.string,
|
||||||
@ -249,7 +251,9 @@ Home.propTypes = {
|
|||||||
toggleWindowFocus: PropTypes.func.isRequired,
|
toggleWindowFocus: PropTypes.func.isRequired,
|
||||||
faviconCount: PropTypes.number.isRequired,
|
faviconCount: PropTypes.number.isRequired,
|
||||||
soundIsEnabled: PropTypes.bool.isRequired,
|
soundIsEnabled: PropTypes.bool.isRequired,
|
||||||
|
persistenceIsEnabled: PropTypes.bool.isRequired,
|
||||||
toggleSoundEnabled: PropTypes.func.isRequired,
|
toggleSoundEnabled: PropTypes.func.isRequired,
|
||||||
|
togglePersistenceEnabled: PropTypes.func.isRequired,
|
||||||
notificationIsEnabled: PropTypes.bool.isRequired,
|
notificationIsEnabled: PropTypes.bool.isRequired,
|
||||||
notificationIsAllowed: PropTypes.bool,
|
notificationIsAllowed: PropTypes.bool,
|
||||||
toggleNotificationEnabled: PropTypes.func.isRequired,
|
toggleNotificationEnabled: PropTypes.func.isRequired,
|
||||||
|
@ -95,7 +95,6 @@ const WithNewMessageNotification = WrappedComponent => {
|
|||||||
switch (Notification.permission) {
|
switch (Notification.permission) {
|
||||||
case 'granted':
|
case 'granted':
|
||||||
this.props.toggleNotificationAllowed(true);
|
this.props.toggleNotificationAllowed(true);
|
||||||
this.props.toggleNotificationEnabled(true);
|
|
||||||
break;
|
break;
|
||||||
case 'denied':
|
case 'denied':
|
||||||
this.props.toggleNotificationAllowed(false);
|
this.props.toggleNotificationAllowed(false);
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
|
togglePersistenceEnabled,
|
||||||
toggleNotificationEnabled,
|
toggleNotificationEnabled,
|
||||||
toggleNotificationAllowed,
|
toggleNotificationAllowed,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
@ -36,6 +37,7 @@ const mapStateToProps = state => {
|
|||||||
iAmOwner: Boolean(me && me.isOwner),
|
iAmOwner: Boolean(me && me.isOwner),
|
||||||
faviconCount: state.app.unreadMessageCount,
|
faviconCount: state.app.unreadMessageCount,
|
||||||
soundIsEnabled: state.app.soundIsEnabled,
|
soundIsEnabled: state.app.soundIsEnabled,
|
||||||
|
persistenceIsEnabled: state.app.persistenceIsEnabled,
|
||||||
notificationIsEnabled: state.app.notificationIsEnabled,
|
notificationIsEnabled: state.app.notificationIsEnabled,
|
||||||
notificationIsAllowed: state.app.notificationIsAllowed,
|
notificationIsAllowed: state.app.notificationIsAllowed,
|
||||||
socketConnected: state.app.socketConnected,
|
socketConnected: state.app.socketConnected,
|
||||||
@ -51,6 +53,7 @@ const mapDispatchToProps = {
|
|||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
|
togglePersistenceEnabled,
|
||||||
toggleNotificationEnabled,
|
toggleNotificationEnabled,
|
||||||
toggleNotificationAllowed,
|
toggleNotificationAllowed,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
|
@ -42,6 +42,21 @@ exports[`Settings component > should display 1`] = `
|
|||||||
Desktop Notification
|
Desktop Notification
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="form-check"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="form-check-label"
|
||||||
|
for="persistence-control"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
id="persistence-control"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Persist configuration
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
@ -233,6 +248,21 @@ exports[`Settings component > should display 2`] = `
|
|||||||
Desktop notifications have been denied
|
Desktop notifications have been denied
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="form-check"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="form-check-label"
|
||||||
|
for="persistence-control"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
id="persistence-control"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Persist configuration
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Cookie from 'js-cookie';
|
|
||||||
|
|
||||||
import RoomLink from '@/components/RoomLink';
|
import RoomLink from '@/components/RoomLink';
|
||||||
import T from '@/components/T';
|
import T from '@/components/T';
|
||||||
@ -12,6 +11,10 @@ class Settings extends Component {
|
|||||||
this.props.toggleSoundEnabled(!this.props.soundIsEnabled);
|
this.props.toggleSoundEnabled(!this.props.soundIsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePersistenceToggle() {
|
||||||
|
this.props.togglePersistenceEnabled(!this.props.persistenceIsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
handleNotificationToggle() {
|
handleNotificationToggle() {
|
||||||
Notification.requestPermission().then(permission => {
|
Notification.requestPermission().then(permission => {
|
||||||
if (permission === 'granted') {
|
if (permission === 'granted') {
|
||||||
@ -26,7 +29,6 @@ class Settings extends Component {
|
|||||||
|
|
||||||
handleLanguageChange(evt) {
|
handleLanguageChange(evt) {
|
||||||
const language = evt.target.value;
|
const language = evt.target.value;
|
||||||
Cookie.set('language', language);
|
|
||||||
this.props.setLanguage(language);
|
this.props.setLanguage(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +70,18 @@ class Settings extends Component {
|
|||||||
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<label className="form-check-label" htmlFor="persistence-control">
|
||||||
|
<input
|
||||||
|
id="persistence-control"
|
||||||
|
onChange={this.handlePersistenceToggle.bind(this)}
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
checked={this.props.persistenceIsEnabled}
|
||||||
|
/>
|
||||||
|
<T path="persistence" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -170,6 +184,7 @@ class Settings extends Component {
|
|||||||
|
|
||||||
Settings.propTypes = {
|
Settings.propTypes = {
|
||||||
soundIsEnabled: PropTypes.bool.isRequired,
|
soundIsEnabled: PropTypes.bool.isRequired,
|
||||||
|
persistenceIsEnabled: PropTypes.bool.isRequired,
|
||||||
toggleSoundEnabled: PropTypes.func.isRequired,
|
toggleSoundEnabled: PropTypes.func.isRequired,
|
||||||
notificationIsEnabled: PropTypes.bool.isRequired,
|
notificationIsEnabled: PropTypes.bool.isRequired,
|
||||||
notificationIsAllowed: PropTypes.bool,
|
notificationIsAllowed: PropTypes.bool,
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"lists all commands"
|
"lists all commands"
|
||||||
],
|
],
|
||||||
"sound": "Sound",
|
"sound": "Sound",
|
||||||
|
"persistence": "Persist configuration",
|
||||||
"newMessageNotification": "New message notification",
|
"newMessageNotification": "New message notification",
|
||||||
"desktopNotification": "Desktop Notification",
|
"desktopNotification": "Desktop Notification",
|
||||||
"desktopNotificationBlocked": "Desktop notifications have been denied",
|
"desktopNotificationBlocked": "Desktop notifications have been denied",
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"lister toutes les commandes"
|
"lister toutes les commandes"
|
||||||
],
|
],
|
||||||
"sound": "Son",
|
"sound": "Son",
|
||||||
|
"persistence": "Mémoriser la configuration",
|
||||||
"newMessageNotification": "Notification lors d'un nouveau message",
|
"newMessageNotification": "Notification lors d'un nouveau message",
|
||||||
"desktopNotification": "Notification Système",
|
"desktopNotification": "Notification Système",
|
||||||
"desktopNotificationBlocked": "Les notifications systèmes ont été refusée",
|
"desktopNotificationBlocked": "Les notifications systèmes ont été refusée",
|
||||||
|
@ -12,8 +12,10 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|||||||
import configureStore from '@/store/';
|
import configureStore from '@/store/';
|
||||||
import Home from '@/components/Home/';
|
import Home from '@/components/Home/';
|
||||||
import { hasTouchSupport } from '@/utils/dom';
|
import { hasTouchSupport } from '@/utils/dom';
|
||||||
|
import { loadPersistedState, persistState } from '@/utils/persistence';
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore(loadPersistedState());
|
||||||
|
store.subscribe(() => persistState(store));
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import Cookie from 'js-cookie';
|
|
||||||
|
|
||||||
import { getTranslations } from '@/i18n';
|
import { getTranslations } from '@/i18n';
|
||||||
|
|
||||||
const language = Cookie.get('language') || navigator.language || 'en';
|
const language = navigator.language || 'en';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
modalComponent: null,
|
modalComponent: null,
|
||||||
@ -10,14 +8,17 @@ const initialState = {
|
|||||||
windowIsFocused: true,
|
windowIsFocused: true,
|
||||||
unreadMessageCount: 0,
|
unreadMessageCount: 0,
|
||||||
soundIsEnabled: true,
|
soundIsEnabled: true,
|
||||||
notificationIsEnabled: false,
|
persistenceIsEnabled: false,
|
||||||
|
notificationIsEnabled: true,
|
||||||
notificationIsAllowed: null,
|
notificationIsAllowed: null,
|
||||||
socketConnected: false,
|
socketConnected: false,
|
||||||
language,
|
language,
|
||||||
translations: getTranslations(language),
|
translations: getTranslations(language),
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = (state = initialState, action) => {
|
const app = (receivedState, action) => {
|
||||||
|
const state = { ...initialState, ...receivedState };
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'OPEN_MODAL':
|
case 'OPEN_MODAL':
|
||||||
return {
|
return {
|
||||||
@ -50,6 +51,11 @@ const app = (state = initialState, action) => {
|
|||||||
...state,
|
...state,
|
||||||
soundIsEnabled: action.payload,
|
soundIsEnabled: action.payload,
|
||||||
};
|
};
|
||||||
|
case 'TOGGLE_PERSISTENCE_ENABLED':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
persistenceIsEnabled: action.payload,
|
||||||
|
};
|
||||||
case 'TOGGLE_NOTIFICATION_ENABLED':
|
case 'TOGGLE_NOTIFICATION_ENABLED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -16,9 +16,10 @@ describe('App reducer', () => {
|
|||||||
modalComponent: null,
|
modalComponent: null,
|
||||||
scrolledToBottom: true,
|
scrolledToBottom: true,
|
||||||
socketConnected: false,
|
socketConnected: false,
|
||||||
notificationIsEnabled: false,
|
notificationIsEnabled: true,
|
||||||
notificationIsAllowed: null,
|
notificationIsAllowed: null,
|
||||||
soundIsEnabled: true,
|
soundIsEnabled: true,
|
||||||
|
persistenceIsEnabled: false,
|
||||||
translations: { path: 'test' },
|
translations: { path: 'test' },
|
||||||
unreadMessageCount: 0,
|
unreadMessageCount: 0,
|
||||||
windowIsFocused: true,
|
windowIsFocused: true,
|
||||||
@ -26,51 +27,65 @@ describe('App reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle OPEN_MODAL', () => {
|
it('should handle OPEN_MODAL', () => {
|
||||||
expect(reducer({}, { type: 'OPEN_MODAL', payload: 'test' })).toEqual({ modalComponent: 'test' });
|
expect(reducer({}, { type: 'OPEN_MODAL', payload: 'test' })).toEqual(
|
||||||
|
expect.objectContaining({ modalComponent: 'test' }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLOSE_MODAL', () => {
|
it('should handle CLOSE_MODAL', () => {
|
||||||
expect(reducer({}, { type: 'CLOSE_MODAL' })).toEqual({ modalComponent: null });
|
expect(reducer({}, { type: 'CLOSE_MODAL' })).toEqual(expect.objectContaining({ modalComponent: null }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle SET_SCROLLED_TO_BOTTOM', () => {
|
it('should handle SET_SCROLLED_TO_BOTTOM', () => {
|
||||||
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: true })).toEqual({ scrolledToBottom: true });
|
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: true })).toEqual(
|
||||||
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: false })).toEqual({ scrolledToBottom: false });
|
expect.objectContaining({ scrolledToBottom: true }),
|
||||||
|
);
|
||||||
|
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: false })).toEqual(
|
||||||
|
expect.objectContaining({ scrolledToBottom: false }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_WINDOW_FOCUS', () => {
|
it('should handle TOGGLE_WINDOW_FOCUS', () => {
|
||||||
expect(reducer({ unreadMessageCount: 10 }, { type: 'TOGGLE_WINDOW_FOCUS', payload: true })).toEqual({
|
expect(reducer({ unreadMessageCount: 10 }, { type: 'TOGGLE_WINDOW_FOCUS', payload: true })).toEqual(
|
||||||
windowIsFocused: true,
|
expect.objectContaining({
|
||||||
unreadMessageCount: 0,
|
windowIsFocused: true,
|
||||||
});
|
unreadMessageCount: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
|
it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
|
||||||
expect(
|
expect(
|
||||||
reducer({ unreadMessageCount: 10, windowIsFocused: false }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
reducer({ unreadMessageCount: 10, windowIsFocused: false }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
||||||
).toEqual({ unreadMessageCount: 11, windowIsFocused: false });
|
).toEqual(expect.objectContaining({ unreadMessageCount: 11, windowIsFocused: false }));
|
||||||
expect(
|
expect(
|
||||||
reducer({ unreadMessageCount: 10, windowIsFocused: true }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
reducer({ unreadMessageCount: 10, windowIsFocused: true }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
||||||
).toEqual({ unreadMessageCount: 0, windowIsFocused: true });
|
).toEqual(expect.objectContaining({ unreadMessageCount: 0, windowIsFocused: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_SOUND_ENABLED', () => {
|
it('should handle TOGGLE_SOUND_ENABLED', () => {
|
||||||
expect(reducer({}, { type: 'TOGGLE_SOUND_ENABLED', payload: true })).toEqual({
|
expect(reducer({}, { type: 'TOGGLE_SOUND_ENABLED', payload: true })).toEqual(
|
||||||
soundIsEnabled: true,
|
expect.objectContaining({
|
||||||
});
|
soundIsEnabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_SOCKET_CONNECTED', () => {
|
it('should handle TOGGLE_SOCKET_CONNECTED', () => {
|
||||||
expect(reducer({}, { type: 'TOGGLE_SOCKET_CONNECTED', payload: true })).toEqual({
|
expect(reducer({}, { type: 'TOGGLE_SOCKET_CONNECTED', payload: true })).toEqual(
|
||||||
socketConnected: true,
|
expect.objectContaining({
|
||||||
});
|
socketConnected: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CHANGE_LANGUAGE', () => {
|
it('should handle CHANGE_LANGUAGE', () => {
|
||||||
getTranslations.mockReturnValueOnce({ path: 'new lang' });
|
getTranslations.mockReturnValueOnce({ path: 'new lang' });
|
||||||
expect(reducer({}, { type: 'CHANGE_LANGUAGE', payload: 'fr' })).toEqual({
|
expect(reducer({}, { type: 'CHANGE_LANGUAGE', payload: 'fr' })).toEqual(
|
||||||
language: 'fr',
|
expect.objectContaining({
|
||||||
translations: { path: 'new lang' },
|
language: 'fr',
|
||||||
});
|
translations: { path: 'new lang' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,9 @@ const initialState = {
|
|||||||
id: '',
|
id: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = (state = initialState, action) => {
|
const user = (receivedState, action) => {
|
||||||
|
const state = { ...initialState, ...receivedState };
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'CREATE_USER':
|
case 'CREATE_USER':
|
||||||
return {
|
return {
|
||||||
|
@ -25,6 +25,9 @@ describe('User reducer', () => {
|
|||||||
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
||||||
const payload = { newUsername: 'alice' };
|
const payload = { newUsername: 'alice' };
|
||||||
expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
|
expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
|
||||||
|
id: '',
|
||||||
|
privateKey: {},
|
||||||
|
publicKey: {},
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
import 'react-simple-dropdown/styles/Dropdown.css';
|
|
||||||
import 'stylesheets/app.sass';
|
|
||||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { Redirect } from 'react-router';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import configureStore from './store';
|
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
|
||||||
import shortId from 'shortid';
|
|
||||||
import Home from 'components/Home';
|
|
||||||
import { hasTouchSupport } from './utils/dom';
|
|
||||||
|
|
||||||
const store = configureStore();
|
|
||||||
|
|
||||||
export default class Root extends Component {
|
|
||||||
componentWillMount() {
|
|
||||||
if (hasTouchSupport) {
|
|
||||||
document.body.classList.add('touch');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Provider store={store}>
|
|
||||||
<BrowserRouter>
|
|
||||||
<div className="h-100">
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/" render={() => <Redirect to={`/${shortId.generate()}`} />} />
|
|
||||||
<Route path="/:roomId" component={Home} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
83
client/src/utils/persistence.js
Normal file
83
client/src/utils/persistence.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Handle localStorage persistence
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { getTranslations } from '@/i18n';
|
||||||
|
|
||||||
|
const getSettings = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem('settings')) || {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSettings = settings => {
|
||||||
|
localStorage.setItem('settings', JSON.stringify(settings));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPersistedState = () => {
|
||||||
|
const state = {};
|
||||||
|
|
||||||
|
const stored = getSettings();
|
||||||
|
|
||||||
|
if (stored.persistenceIsEnabled !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.app = {};
|
||||||
|
if (stored.language) {
|
||||||
|
state.app.language = stored.language;
|
||||||
|
state.app.translations = getTranslations(stored.language);
|
||||||
|
}
|
||||||
|
state.user = {};
|
||||||
|
if (stored.username) {
|
||||||
|
state.user.username = stored.username;
|
||||||
|
}
|
||||||
|
state.app.soundIsEnabled = stored.soundIsEnabled !== false;
|
||||||
|
state.app.persistenceIsEnabled = stored.persistenceIsEnabled === true;
|
||||||
|
state.app.notificationIsEnabled = stored.notificationIsEnabled !== false;
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
let prevState;
|
||||||
|
|
||||||
|
export const persistState = debounce(async store => {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
// We need prev state to compare
|
||||||
|
if (prevState) {
|
||||||
|
const {
|
||||||
|
user: { username },
|
||||||
|
app: { notificationIsEnabled, soundIsEnabled, persistenceIsEnabled, language },
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
|
if (!persistenceIsEnabled) {
|
||||||
|
setSettings({ persistenceIsEnabled: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stored = getSettings();
|
||||||
|
|
||||||
|
if (prevState.user.notificationIsEnabled !== notificationIsEnabled) {
|
||||||
|
stored.notificationIsEnabled = notificationIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.app.soundIsEnabled !== soundIsEnabled) {
|
||||||
|
stored.soundIsEnabled = soundIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.app.persistenceIsEnabled !== persistenceIsEnabled) {
|
||||||
|
stored.persistenceIsEnabled = persistenceIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.user.username !== username && username) {
|
||||||
|
stored.username = username;
|
||||||
|
}
|
||||||
|
if (prevState.app.language !== language && language) {
|
||||||
|
stored.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSettings(stored);
|
||||||
|
}
|
||||||
|
prevState = JSON.parse(JSON.stringify(state));
|
||||||
|
}, 1000);
|
@ -2185,11 +2185,6 @@ jquery@3:
|
|||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16"
|
||||||
integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==
|
integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==
|
||||||
|
|
||||||
js-cookie@^3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
|
|
||||||
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
|
|
||||||
|
|
||||||
js-sdsl@^4.1.4:
|
js-sdsl@^4.1.4:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
|
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user