Move App into shell dir. Decouple ContextProviders. Improved type coverage for reducers

This commit is contained in:
SleepWalker 2019-12-09 09:07:07 +02:00
parent 5c70021456
commit 128f327ec4
10 changed files with 107 additions and 73 deletions

View File

@ -1,34 +0,0 @@
import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Provider as ReduxProvider } from 'react-redux';
import { Router, Route, Switch } from 'react-router-dom';
import { IntlProvider } from 'app/components/i18n';
import { Store } from 'redux';
import AuthFlowRoute from 'app/containers/AuthFlowRoute';
import RootPage from 'app/pages/root/RootPage';
import SuccessOauthPage from 'app/pages/auth/SuccessOauthPage';
const App = ({
store,
browserHistory,
}: {
store: Store;
browserHistory: any;
}) => (
<ReduxProvider store={store}>
<IntlProvider>
<Router history={browserHistory}>
<Switch>
<Route path="/oauth2/code/success" component={SuccessOauthPage} />
<AuthFlowRoute
path="/oauth2/:version(v\d+)/:clientId?"
component={() => null}
/>
<Route path="/" component={RootPage} />
</Switch>
</Router>
</IntlProvider>
</ReduxProvider>
);
export default hot(App);

View File

@ -1,4 +1,5 @@
import { combineReducers } from 'redux';
import { RootState } from 'app/reducers';
import {
ERROR,
@ -27,9 +28,25 @@ export interface Client {
description: string;
}
interface OAuthState {
clientId: string;
redirectUrl: string;
responseType: string;
description?: string;
scope: string;
prompt: string;
loginHint: string;
state: string;
success?: boolean;
code?: string;
displayCode?: string;
acceptRequired?: boolean;
}
export interface State {
credentials: Credentials;
error:
| null
| string
| {
type: string;
@ -38,25 +55,11 @@ export interface State {
isLoading: boolean;
isSwitcherEnabled: boolean;
client: Client | null;
login: string;
oauth: {
clientId: string;
redirectUrl: string;
responseType: string;
description: string;
scope: string;
prompt: string;
loginHint: string;
state: string;
success?: boolean;
code?: string;
displayCode?: string;
acceptRequired?: boolean;
} | null;
oauth: OAuthState | null;
scopes: string[];
}
export default combineReducers({
export default combineReducers<State>({
credentials,
error,
isLoading,
@ -66,7 +69,10 @@ export default combineReducers({
scopes,
});
function error(state = null, { type, payload = null, error = false }) {
function error(
state = null,
{ type, payload = null, error = false },
): State['error'] {
switch (type) {
case ERROR:
if (!error) {
@ -89,7 +95,7 @@ function credentials(
type: string;
payload: Credentials | null;
},
) {
): State['credentials'] {
if (type === SET_CREDENTIALS) {
if (payload && typeof payload === 'object') {
return {
@ -103,7 +109,10 @@ function credentials(
return state;
}
function isSwitcherEnabled(state = true, { type, payload = false }) {
function isSwitcherEnabled(
state = true,
{ type, payload = false },
): State['isSwitcherEnabled'] {
switch (type) {
case SET_SWITCHER:
if (typeof payload !== 'boolean') {
@ -117,7 +126,10 @@ function isSwitcherEnabled(state = true, { type, payload = false }) {
}
}
function isLoading(state = false, { type, payload = null }) {
function isLoading(
state = false,
{ type, payload = null },
): State['isLoading'] {
switch (type) {
case SET_LOADING_STATE:
return !!payload;
@ -127,7 +139,7 @@ function isLoading(state = false, { type, payload = null }) {
}
}
function client(state = null, { type, payload }) {
function client(state = null, { type, payload }): State['client'] {
switch (type) {
case SET_CLIENT:
return {
@ -141,7 +153,10 @@ function client(state = null, { type, payload }) {
}
}
function oauth(state: State | null = null, { type, payload }) {
function oauth(
state: State['oauth'] = null,
{ type, payload },
): State['oauth'] {
switch (type) {
case SET_OAUTH:
return {
@ -156,7 +171,7 @@ function oauth(state: State | null = null, { type, payload }) {
case SET_OAUTH_RESULT:
return {
...state,
...(state as OAuthState),
success: payload.success,
code: payload.code,
displayCode: payload.displayCode,
@ -164,7 +179,7 @@ function oauth(state: State | null = null, { type, payload }) {
case REQUIRE_PERMISSIONS_ACCEPT:
return {
...state,
...(state as OAuthState),
acceptRequired: true,
};
@ -173,7 +188,7 @@ function oauth(state: State | null = null, { type, payload }) {
}
}
function scopes(state = [], { type, payload = [] }) {
function scopes(state = [], { type, payload = [] }): State['scopes'] {
switch (type) {
case SET_SCOPES:
return payload;
@ -183,10 +198,10 @@ function scopes(state = [], { type, payload = [] }) {
}
}
export function getLogin(state: { [key: string]: any }): string | null {
export function getLogin(state: RootState): string | null {
return state.auth.credentials.login || null;
}
export function getCredentials(state: { [key: string]: any }): Credentials {
export function getCredentials(state: RootState): Credentials {
return state.auth.credentials;
}

View File

@ -1,6 +1,8 @@
import { BSOD } from './actions';
export default function(state = false, { type }) {
export type State = boolean;
export default function(state: State = false, { type }): State {
if (type === BSOD) {
return true;
}

View File

@ -14,7 +14,7 @@ import history, { browserHistory } from 'app/services/history';
import i18n from 'app/services/i18n';
import { loadScript, debounce } from 'app/functions';
import App from './App';
import App from './shell';
const win: { [key: string]: any } = window as any;
@ -35,7 +35,7 @@ Promise.all([
i18n.ensureIntl(), // ensure, that intl is polyfilled before any rendering
]).then(() => {
ReactDOM.render(
<App store={store} browserHistory={browserHistory} />,
<App store={store} history={browserHistory} />,
document.getElementById('app'),
);

View File

@ -7,13 +7,14 @@ import accounts, {
} from 'app/components/accounts/reducer';
import i18n, { State as I18nState } from 'app/components/i18n/reducer';
import popup, { State as PopupState } from 'app/components/ui/popup/reducer';
import bsod from 'app/components/ui/bsod/reducer';
import bsod, { State as BsodState } from 'app/components/ui/bsod/reducer';
import apps, { Apps } from 'app/components/dev/apps/reducer';
import { ThunkDispatch, ThunkAction as ReduxThunkAction } from 'redux-thunk';
import { Store as ReduxStore } from 'redux';
export interface RootState {
auth: AuthState;
bsod: BsodState;
accounts: AccountsState;
user: User;
popup: PopupState;
@ -41,7 +42,7 @@ export type Store = ReduxStore<RootState> & {
dispatch: Dispatch;
};
export default combineReducers({
export default combineReducers<RootState>({
bsod,
auth,
user,

View File

@ -33,7 +33,7 @@ type OauthRequestData = {
client_id: string;
redirect_uri: string;
response_type: string;
description: string;
description?: string;
scope: string;
prompt: string;
login_hint?: string;
@ -44,7 +44,7 @@ export type OauthData = {
clientId: string;
redirectUrl: string;
responseType: string;
description: string;
description?: string;
scope: string;
prompt: string; // comma separated list of 'none' | 'consent' | 'select_account';
loginHint?: string;

View File

@ -0,0 +1,24 @@
import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Route, Switch } from 'react-router-dom';
import { Store } from 'app/reducers';
import AuthFlowRoute from 'app/containers/AuthFlowRoute';
import RootPage from 'app/pages/root/RootPage';
import SuccessOauthPage from 'app/pages/auth/SuccessOauthPage';
import ContextProvider from './ContextProvider';
const App = ({ store, history }: { store: Store; history: any }) => (
<ContextProvider store={store} history={history}>
<Switch>
<Route path="/oauth2/code/success" component={SuccessOauthPage} />
<AuthFlowRoute
path="/oauth2/:version(v\d+)/:clientId?"
component={() => null}
/>
<Route path="/" component={RootPage} />
</Switch>
</ContextProvider>
);
export default hot(App);

View File

@ -0,0 +1,25 @@
import React from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import { Router } from 'react-router-dom';
import { IntlProvider } from 'app/components/i18n';
import { Store } from 'app/reducers';
function ContextProvider({
children,
store,
history,
}: {
children: React.ReactNode;
store: Store;
history: any;
}) {
return (
<ReduxProvider store={store}>
<IntlProvider>
<Router history={history}>{children}</Router>
</IntlProvider>
</ReduxProvider>
);
}
export default ContextProvider;

View File

@ -0,0 +1,2 @@
export { default } from './App';
export { default as ContextProvider } from './ContextProvider';

View File

@ -6,26 +6,25 @@ import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import persistState from 'redux-localstorage';
import reducers from 'app/reducers';
import reducers, { Store } from 'app/reducers';
export default function storeFactory() {
export default function storeFactory(): Store {
const middlewares = applyMiddleware(thunk);
const persistStateEnhancer = persistState(['accounts', 'user'], {
key: 'redux-storage',
});
/* global process: false */
let enhancer;
if (process.env.NODE_ENV === 'production') {
enhancer = compose(middlewares, persistStateEnhancer);
} else {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
enhancer = composeEnhancers(middlewares, persistStateEnhancer);
}
const store = createStore(reducers, {}, enhancer);
const store = createStore(reducers, {}, enhancer) as Store;
// Hot reload reducers
if (module.hot && typeof module.hot.accept === 'function') {