Add a basic redux store.

master
Tom Hacohen 7 years ago
parent 55f595d52a
commit 94c6916447

@ -10,8 +10,13 @@
"react": "^16.2.0",
"react-big-calendar": "^0.17.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"react-scripts-ts": "2.8.0",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-persist": "^5.4.0",
"redux-thunk": "^2.2.0",
"sjcl": "git+https://github.com/etesync/sjcl",
"urijs": "^1.16.1"
},
@ -29,8 +34,10 @@
"@types/react": "^16.0.25",
"@types/react-big-calendar": "^0.15.0",
"@types/react-dom": "^16.0.3",
"@types/react-redux": "^5.0.14",
"@types/react-router": "^4.0.19",
"@types/react-router-dom": "^4.2.3",
"@types/redux-logger": "^3.0.5",
"@types/sjcl": "^1.0.28",
"@types/urijs": "^1.15.34"
}

@ -1,5 +1,6 @@
import * as React from 'react';
import { Switch, Route, Redirect } from 'react-router';
import { connect } from 'react-redux';
import { Switch, Route, Redirect, withRouter } from 'react-router';
import Paper from 'material-ui/Paper';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
@ -11,17 +12,10 @@ import { JournalView } from './JournalView';
import * as EteSync from './api/EteSync';
import { routeResolver, getPalette } from './App';
import * as store from './store';
import * as C from './Constants';
const CONTEXT_SESSION_KEY = 'EteSyncContext';
enum LoadState {
Initial = 'INIT',
Working = 'WORKING',
Done = 'DONE',
}
export interface EteSyncContextType {
serviceApiUrl: string;
credentials: EteSync.Credentials;
@ -35,12 +29,35 @@ interface FormErrors {
errorServer?: string;
}
export class EteSyncContext extends React.Component {
function fetchCredentials(username: string, password: string, encryptionPassword: string, server: string) {
const authenticator = new EteSync.Authenticator(server);
return (dispatch: any) => {
dispatch(store.credentialsRequest());
authenticator.getAuthToken(username, password).then(
(authToken) => {
const credentials = new EteSync.Credentials(username, authToken);
const derived = EteSync.deriveKey(username, encryptionPassword);
const context = {
serviceApiUrl: server,
credentials,
encryptionKey: derived,
};
dispatch(store.credentialsSuccess(context));
},
(error) => {
dispatch(store.credentialsFailure(error));
}
);
};
}
export class EteSyncContextInner extends React.Component {
state: {
context?: EteSyncContextType;
loadState: LoadState;
showAdvanced?: boolean;
error?: Error;
errors: FormErrors;
server: string;
@ -49,10 +66,13 @@ export class EteSyncContext extends React.Component {
encryptionPassword: string;
};
props: {
credentials: store.CredentialsType;
};
constructor(props: any) {
super(props);
this.state = {
loadState: LoadState.Initial,
errors: {},
server: '',
username: '',
@ -62,17 +82,6 @@ export class EteSyncContext extends React.Component {
this.generateEncryption = this.generateEncryption.bind(this);
this.toggleAdvancedSettings = this.toggleAdvancedSettings.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
const contextStr = sessionStorage.getItem(CONTEXT_SESSION_KEY);
if (contextStr !== null) {
const context: EteSyncContextType = JSON.parse(contextStr);
this.state = Object.assign({}, this.state, {
loadState: LoadState.Done,
context
});
}
}
handleInputChange(event: any) {
@ -87,8 +96,6 @@ export class EteSyncContext extends React.Component {
e.preventDefault();
const server = this.state.showAdvanced ? this.state.server : C.serviceApiBase;
let authenticator = new EteSync.Authenticator(server);
const username = this.state.username;
const password = this.state.password;
const encryptionPassword = this.state.encryptionPassword;
@ -109,32 +116,7 @@ export class EteSyncContext extends React.Component {
return;
}
this.setState({
loadState: LoadState.Working
});
authenticator.getAuthToken(username, password).then((authToken) => {
const credentials = new EteSync.Credentials(username, authToken);
const derived = EteSync.deriveKey(username, encryptionPassword);
const context = {
serviceApiUrl: server,
credentials,
encryptionKey: derived,
};
sessionStorage.setItem(CONTEXT_SESSION_KEY, JSON.stringify(context));
this.setState({
loadState: LoadState.Done,
context
});
}).catch((error) => {
this.setState({
loadState: LoadState.Initial,
error
});
});
store.store.dispatch(fetchCredentials(username, password, encryptionPassword, server));
}
toggleAdvancedSettings() {
@ -142,7 +124,10 @@ export class EteSyncContext extends React.Component {
}
render() {
if (this.state.loadState === LoadState.Initial) {
if (((this.props.credentials.status === store.FetchStatus.Initial) &&
(this.props.credentials.credentials === undefined)) ||
(this.props.credentials.status === store.FetchStatus.Failure)) {
let advancedSettings = null;
if (this.state.showAdvanced) {
advancedSettings = (
@ -187,7 +172,7 @@ export class EteSyncContext extends React.Component {
return (
<div style={styles.holder}>
<Paper zDepth={2} style={styles.paper}>
{(this.state.error !== undefined) && (<div>Error! {this.state.error.message}</div>)}
{(this.props.credentials.error !== undefined) && (<div>Error! {this.props.credentials.error.message}</div>)}
<h2>Please Log In</h2>
<form style={styles.form} onSubmit={this.generateEncryption}>
<TextField
@ -231,12 +216,11 @@ export class EteSyncContext extends React.Component {
</Paper>
</div>
);
} else if ((this.state.context === undefined) ||
(this.state.loadState === LoadState.Working)) {
} else if (this.props.credentials.status === store.FetchStatus.Request) {
return (<div>Loading</div>);
}
let context: EteSyncContextType = this.state.context;
let context = this.props.credentials.credentials as store.CredentialsData;
return (
<div>
@ -260,3 +244,13 @@ export class EteSyncContext extends React.Component {
);
}
}
const mapStateToProps = (state: store.StoreState) => {
return {
credentials: state.credentials,
};
};
export const EteSyncContext = withRouter(connect(
mapStateToProps
)(EteSyncContextInner));

@ -1,11 +1,19 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/es/integration/react';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import { store, persistor } from './store';
ReactDOM.render(
<App />,
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();

@ -0,0 +1,3 @@
declare module 'redux-persist';
declare module 'redux-persist/lib/storage/session';
declare module 'redux-persist/es/integration/react';

@ -0,0 +1,113 @@
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { persistReducer, persistStore } from 'redux-persist';
import session from 'redux-persist/lib/storage/session';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import * as EteSync from './api/EteSync';
const loggerMiddleware = createLogger();
enum Actions {
FETCH_CREDENTIALS = 'FETCH_CREDENTIALS',
}
export enum FetchStatus {
Initial = 'INITIAL',
Request = 'REQUEST',
Failure = 'FAILURE',
Success = 'SUCCESS',
}
export interface CredentialsData {
serviceApiUrl: string;
credentials: EteSync.Credentials;
encryptionKey: string;
}
export interface CredentialsType {
status: FetchStatus;
error?: Error;
credentials?: CredentialsData;
}
export interface StoreState {
fetchCount: number;
credentials: CredentialsData;
}
export function credentialsSuccess(creds: CredentialsData) {
return {
type: Actions.FETCH_CREDENTIALS,
status: FetchStatus.Success,
credentials: creds,
};
}
export function credentialsRequest() {
return {
type: Actions.FETCH_CREDENTIALS,
status: FetchStatus.Request,
};
}
export function credentialsFailure(error: Error) {
return {
type: Actions.FETCH_CREDENTIALS,
status: FetchStatus.Failure,
error
};
}
function credentials(state: CredentialsType = {status: FetchStatus.Initial}, action: any) {
switch (action.type) {
case Actions.FETCH_CREDENTIALS:
if (action.status === FetchStatus.Success) {
return {
status: action.status,
credentials: action.credentials,
};
} else {
return {
status: action.status,
};
}
default:
return state;
}
}
function fetchCount(state: number = 0, action: any) {
// FIXME: Make it automatic by action properties.
switch (action.type) {
case Actions.FETCH_CREDENTIALS:
if (action.status === FetchStatus.Request) {
return state + 1;
} else {
return state - 1;
}
default:
return state;
}
}
const credentialsPersistConfig = {
key: 'credentials',
storage: session,
whitelist: ['credentials'],
};
const reducers = combineReducers({
fetchCount,
credentials: persistReducer(credentialsPersistConfig, credentials),
});
export const store = createStore(
reducers,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
);
export const persistor = persistStore(store);

@ -48,6 +48,13 @@
"@types/node" "*"
"@types/react" "*"
"@types/react-redux@^5.0.14":
version "5.0.14"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-5.0.14.tgz#f3fc30dcbb2d20455a714f591cc27f77b4df09bb"
dependencies:
"@types/react" "*"
redux "^3.6.0"
"@types/react-router-dom@^4.2.3":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.3.tgz#06e0b67ff536adc0681dffdbe592ae91fb85887d"
@ -67,6 +74,12 @@
version "16.0.25"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed"
"@types/redux-logger@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.5.tgz#d1a02758f90845899cd304aa0912daeba2028eb6"
dependencies:
redux "^3.6.0"
"@types/sjcl@^1.0.28":
version "1.0.28"
resolved "https://registry.yarnpkg.com/@types/sjcl/-/sjcl-1.0.28.tgz#4693eb6943e385e844a70fb25b4699db286c7214"
@ -1310,6 +1323,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -2297,7 +2314,7 @@ hoek@4.x.x:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1:
hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@ -2526,7 +2543,7 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2:
invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
@ -3240,6 +3257,10 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
lodash-es@^4.2.0, lodash-es@^4.2.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@ -3281,7 +3302,7 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0:
"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@ -4513,6 +4534,17 @@ react-prop-types@^0.4.0:
dependencies:
warning "^3.0.0"
react-redux@^5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
dependencies:
hoist-non-react-statics "^2.2.1"
invariant "^2.0.0"
lodash "^4.2.0"
lodash-es "^4.2.0"
loose-envify "^1.1.0"
prop-types "^15.5.10"
react-router-dom@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"
@ -4694,6 +4726,29 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
redux-logger@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
dependencies:
deep-diff "^0.3.5"
redux-persist@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-5.4.0.tgz#a1062313546a9d4ca6f9271464d18f736e8ca394"
redux-thunk@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
redux@^3.6.0, redux@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
dependencies:
lodash "^4.2.1"
lodash-es "^4.2.1"
loose-envify "^1.1.0"
symbol-observable "^1.0.3"
regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
@ -5355,7 +5410,7 @@ sw-toolbox@^3.4.0:
path-to-regexp "^1.0.1"
serviceworker-cache-polyfill "^4.0.0"
symbol-observable@^1.0.4:
symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"

Loading…
Cancel
Save