refactor: remove immutable from status and auth (#4727)
This commit is contained in:
parent
bf84b6767d
commit
655fffe7c9
@ -1,8 +1,8 @@
|
|||||||
import { State } from '../types/redux';
|
|
||||||
import { currentBackend } from '../backend';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { AnyAction } from 'redux';
|
import { AnyAction } from 'redux';
|
||||||
import { actions as notifActions } from 'redux-notifications';
|
import { actions as notifActions } from 'redux-notifications';
|
||||||
|
import { State } from '../types/redux';
|
||||||
|
import { currentBackend } from '../backend';
|
||||||
|
|
||||||
const { notifSend, notifDismiss } = notifActions;
|
const { notifSend, notifDismiss } = notifActions;
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export function checkBackendStatus() {
|
|||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
try {
|
try {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
if (state.status.get('isFetching')) {
|
if (state.status.isFetching) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +73,13 @@ const RouteInCollection = ({ collections, render, ...props }) => {
|
|||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
auth: ImmutablePropTypes.map,
|
auth: PropTypes.object.isRequired,
|
||||||
config: ImmutablePropTypes.map,
|
config: ImmutablePropTypes.map,
|
||||||
collections: ImmutablePropTypes.orderedMap,
|
collections: ImmutablePropTypes.orderedMap,
|
||||||
loadConfig: PropTypes.func.isRequired,
|
loadConfig: PropTypes.func.isRequired,
|
||||||
loginUser: PropTypes.func.isRequired,
|
loginUser: PropTypes.func.isRequired,
|
||||||
logoutUser: PropTypes.func.isRequired,
|
logoutUser: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map,
|
user: PropTypes.object,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
|
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
|
||||||
siteId: PropTypes.string,
|
siteId: PropTypes.string,
|
||||||
@ -129,9 +129,8 @@ class App extends React.Component {
|
|||||||
<Notifs CustomComponent={Toast} />
|
<Notifs CustomComponent={Toast} />
|
||||||
{React.createElement(backend.authComponent(), {
|
{React.createElement(backend.authComponent(), {
|
||||||
onLogin: this.handleLogin.bind(this),
|
onLogin: this.handleLogin.bind(this),
|
||||||
error: auth && auth.get('error'),
|
error: auth.error,
|
||||||
isFetching: auth && auth.get('isFetching'),
|
inProgress: auth.isFetching,
|
||||||
inProgress: (auth && auth.get('isFetching')) || false,
|
|
||||||
siteId: this.props.config.getIn(['backend', 'site_domain']),
|
siteId: this.props.config.getIn(['backend', 'site_domain']),
|
||||||
base_url: this.props.config.getIn(['backend', 'base_url'], null),
|
base_url: this.props.config.getIn(['backend', 'base_url'], null),
|
||||||
authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']),
|
authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']),
|
||||||
@ -262,7 +261,7 @@ class App extends React.Component {
|
|||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const { auth, config, collections, globalUI, mediaLibrary } = state;
|
const { auth, config, collections, globalUI, mediaLibrary } = state;
|
||||||
const user = auth && auth.get('user');
|
const user = auth.user;
|
||||||
const isFetching = globalUI.get('isFetching');
|
const isFetching = globalUI.get('isFetching');
|
||||||
const publishMode = config && config.get('publish_mode');
|
const publishMode = config && config.get('publish_mode');
|
||||||
const useMediaLibrary = !mediaLibrary.get('externalLibrary');
|
const useMediaLibrary = !mediaLibrary.get('externalLibrary');
|
||||||
|
@ -113,7 +113,7 @@ const AppHeaderNavList = styled.ul`
|
|||||||
|
|
||||||
class Header extends React.Component {
|
class Header extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||||
onCreateEntryClick: PropTypes.func.isRequired,
|
onCreateEntryClick: PropTypes.func.isRequired,
|
||||||
onLogoutClick: PropTypes.func.isRequired,
|
onLogoutClick: PropTypes.func.isRequired,
|
||||||
@ -216,7 +216,7 @@ class Header extends React.Component {
|
|||||||
<SettingsDropdown
|
<SettingsDropdown
|
||||||
displayUrl={displayUrl}
|
displayUrl={displayUrl}
|
||||||
isTestRepo={isTestRepo}
|
isTestRepo={isTestRepo}
|
||||||
imageUrl={user.get('avatar_url')}
|
imageUrl={user?.avatar_url}
|
||||||
onLogoutClick={onLogoutClick}
|
onLogoutClick={onLogoutClick}
|
||||||
/>
|
/>
|
||||||
</AppHeaderActions>
|
</AppHeaderActions>
|
||||||
|
@ -67,7 +67,7 @@ export class Editor extends React.Component {
|
|||||||
deployPreview: ImmutablePropTypes.map,
|
deployPreview: ImmutablePropTypes.map,
|
||||||
loadDeployPreview: PropTypes.func.isRequired,
|
loadDeployPreview: PropTypes.func.isRequired,
|
||||||
currentStatus: PropTypes.string,
|
currentStatus: PropTypes.string,
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: PropTypes.object,
|
||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string,
|
pathname: PropTypes.string,
|
||||||
search: PropTypes.string,
|
search: PropTypes.string,
|
||||||
@ -432,7 +432,7 @@ function mapStateToProps(state, ownProps) {
|
|||||||
const newEntry = ownProps.newRecord === true;
|
const newEntry = ownProps.newRecord === true;
|
||||||
const fields = selectFields(collection, slug);
|
const fields = selectFields(collection, slug);
|
||||||
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
|
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
|
||||||
const user = auth && auth.get('user');
|
const user = auth.user;
|
||||||
const hasChanged = entryDraft.get('hasChanged');
|
const hasChanged = entryDraft.get('hasChanged');
|
||||||
const displayUrl = config.get('display_url');
|
const displayUrl = config.get('display_url');
|
||||||
const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW;
|
const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW;
|
||||||
|
@ -410,7 +410,7 @@ EditorInterface.propTypes = {
|
|||||||
unPublish: PropTypes.func.isRequired,
|
unPublish: PropTypes.func.isRequired,
|
||||||
onDuplicate: PropTypes.func.isRequired,
|
onDuplicate: PropTypes.func.isRequired,
|
||||||
onChangeStatus: PropTypes.func.isRequired,
|
onChangeStatus: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: PropTypes.object,
|
||||||
hasChanged: PropTypes.bool,
|
hasChanged: PropTypes.bool,
|
||||||
displayUrl: PropTypes.string,
|
displayUrl: PropTypes.string,
|
||||||
hasWorkflow: PropTypes.bool,
|
hasWorkflow: PropTypes.bool,
|
||||||
|
@ -233,7 +233,7 @@ class EditorToolbar extends React.Component {
|
|||||||
onDuplicate: PropTypes.func.isRequired,
|
onDuplicate: PropTypes.func.isRequired,
|
||||||
onPublishAndNew: PropTypes.func.isRequired,
|
onPublishAndNew: PropTypes.func.isRequired,
|
||||||
onPublishAndDuplicate: PropTypes.func.isRequired,
|
onPublishAndDuplicate: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: PropTypes.object,
|
||||||
hasChanged: PropTypes.bool,
|
hasChanged: PropTypes.bool,
|
||||||
displayUrl: PropTypes.string,
|
displayUrl: PropTypes.string,
|
||||||
collection: ImmutablePropTypes.map.isRequired,
|
collection: ImmutablePropTypes.map.isRequired,
|
||||||
@ -606,7 +606,7 @@ class EditorToolbar extends React.Component {
|
|||||||
<ToolbarSectionMeta>
|
<ToolbarSectionMeta>
|
||||||
<SettingsDropdown
|
<SettingsDropdown
|
||||||
displayUrl={displayUrl}
|
displayUrl={displayUrl}
|
||||||
imageUrl={user.get('avatar_url')}
|
imageUrl={user?.avatar_url}
|
||||||
onLogoutClick={onLogoutClick}
|
onLogoutClick={onLogoutClick}
|
||||||
/>
|
/>
|
||||||
</ToolbarSectionMeta>
|
</ToolbarSectionMeta>
|
||||||
|
@ -1,32 +1,38 @@
|
|||||||
import { fromJS } from 'immutable';
|
|
||||||
import { authenticating, authenticate, authError, logout } from '../../actions/auth';
|
import { authenticating, authenticate, authError, logout } from '../../actions/auth';
|
||||||
import auth, { defaultState } from '../auth';
|
import auth, { defaultState } from '../auth';
|
||||||
|
|
||||||
describe('auth', () => {
|
describe('auth', () => {
|
||||||
it('should handle an empty state', () => {
|
it('should handle an empty state', () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||||
// @ts-ignore
|
// @ts-ignore auth reducer doesn't accept empty action
|
||||||
expect(auth(undefined, {})).toEqual(defaultState);
|
expect(auth(undefined, {})).toEqual(defaultState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an authentication request', () => {
|
it('should handle an authentication request', () => {
|
||||||
expect(auth(undefined, authenticating())).toEqual(defaultState.set('isFetching', true));
|
expect(auth(undefined, authenticating())).toEqual({
|
||||||
|
...defaultState,
|
||||||
|
isFetching: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle authentication', () => {
|
it('should handle authentication', () => {
|
||||||
const user = { name: 'joe', token: 'token' };
|
const user = { name: 'joe', token: 'token' };
|
||||||
expect(auth(undefined, authenticate(user))).toEqual(defaultState.set('user', fromJS(user)));
|
expect(auth(undefined, authenticate(user))).toEqual({
|
||||||
|
...defaultState,
|
||||||
|
user,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an authentication error', () => {
|
it('should handle an authentication error', () => {
|
||||||
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual(
|
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual({
|
||||||
defaultState.set('error', 'Error: Bad credentials'),
|
...defaultState,
|
||||||
);
|
error: 'Error: Bad credentials',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle logout', () => {
|
it('should handle logout', () => {
|
||||||
const user = { name: 'joe', token: 'token' };
|
const user = { name: 'joe', token: 'token' };
|
||||||
const newState = auth(defaultState.set('user', fromJS(user)), logout());
|
const newState = auth({ ...defaultState, user }, logout());
|
||||||
expect(newState.get('user')).toBeUndefined();
|
expect(newState.user).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { fromJS } from 'immutable';
|
import { produce } from 'immer';
|
||||||
import { User } from 'netlify-cms-lib-util';
|
import { User } from 'netlify-cms-lib-util';
|
||||||
import {
|
import {
|
||||||
AUTH_REQUEST,
|
AUTH_REQUEST,
|
||||||
@ -8,35 +8,37 @@ import {
|
|||||||
LOGOUT,
|
LOGOUT,
|
||||||
AuthAction,
|
AuthAction,
|
||||||
} from '../actions/auth';
|
} from '../actions/auth';
|
||||||
import { StaticallyTypedRecord } from '../types/immutable';
|
|
||||||
|
|
||||||
export type Auth = StaticallyTypedRecord<{
|
export type Auth = {
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
user: StaticallyTypedRecord<User> | undefined;
|
user: User | undefined;
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
}>;
|
};
|
||||||
|
|
||||||
export const defaultState = fromJS({
|
export const defaultState: Auth = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
user: undefined,
|
user: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}) as Auth;
|
|
||||||
|
|
||||||
const auth = (state = defaultState, action: AuthAction) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case AUTH_REQUEST:
|
|
||||||
return state.set('isFetching', true);
|
|
||||||
case AUTH_SUCCESS:
|
|
||||||
return state.set('user', fromJS(action.payload));
|
|
||||||
case AUTH_FAILURE:
|
|
||||||
return state.set('error', action.payload && action.payload.toString());
|
|
||||||
case AUTH_REQUEST_DONE:
|
|
||||||
return state.set('isFetching', false);
|
|
||||||
case LOGOUT:
|
|
||||||
return state.set('user', undefined).set('isFetching', false);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auth = produce((state: Auth, action: AuthAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case AUTH_REQUEST:
|
||||||
|
state.isFetching = true;
|
||||||
|
break;
|
||||||
|
case AUTH_SUCCESS:
|
||||||
|
state.user = action.payload;
|
||||||
|
break;
|
||||||
|
case AUTH_FAILURE:
|
||||||
|
state.error = action.payload && action.payload.toString();
|
||||||
|
break;
|
||||||
|
case AUTH_REQUEST_DONE:
|
||||||
|
state.isFetching = false;
|
||||||
|
break;
|
||||||
|
case LOGOUT:
|
||||||
|
state.user = undefined;
|
||||||
|
state.isFetching = false;
|
||||||
|
}
|
||||||
|
}, defaultState);
|
||||||
|
|
||||||
export default auth;
|
export default auth;
|
||||||
|
@ -1,46 +1,37 @@
|
|||||||
import { fromJS } from 'immutable';
|
import { produce } from 'immer';
|
||||||
import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE, StatusAction } from '../actions/status';
|
import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE, StatusAction } from '../actions/status';
|
||||||
import { StaticallyTypedRecord } from '../types/immutable';
|
|
||||||
|
|
||||||
export type Status = StaticallyTypedRecord<{
|
export type Status = {
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
status: StaticallyTypedRecord<{
|
status: {
|
||||||
auth: StaticallyTypedRecord<{ status: boolean }>;
|
auth: { status: boolean };
|
||||||
api: StaticallyTypedRecord<{ status: boolean; statusPage: string }>;
|
api: { status: boolean; statusPage: string };
|
||||||
}>;
|
};
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
}>;
|
};
|
||||||
|
|
||||||
const defaultState = fromJS({
|
const defaultState: Status = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
status: {
|
status: {
|
||||||
auth: { status: true },
|
auth: { status: true },
|
||||||
api: { status: true, statusPage: '' },
|
api: { status: true, statusPage: '' },
|
||||||
},
|
},
|
||||||
error: undefined,
|
error: undefined,
|
||||||
}) as Status;
|
};
|
||||||
|
|
||||||
const status = (state = defaultState, action: StatusAction) => {
|
const status = produce((state: Status, action: StatusAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case STATUS_REQUEST:
|
case STATUS_REQUEST:
|
||||||
return state.set('isFetching', true);
|
state.isFetching = true;
|
||||||
|
break;
|
||||||
case STATUS_SUCCESS:
|
case STATUS_SUCCESS:
|
||||||
return state.withMutations(map => {
|
state.isFetching = false;
|
||||||
map.set('isFetching', false);
|
state.status = action.payload.status;
|
||||||
map.set('status', fromJS(action.payload.status));
|
break;
|
||||||
});
|
|
||||||
case STATUS_FAILURE:
|
case STATUS_FAILURE:
|
||||||
return state.withMutations(map => {
|
state.isFetching = false;
|
||||||
map.set('isFetching', false);
|
state.error = action.payload.error;
|
||||||
map.set('error', action.payload.error);
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
};
|
}, defaultState);
|
||||||
|
|
||||||
export const selectStatus = (status: Status) => {
|
|
||||||
return status.get('status').toJS();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default status;
|
export default status;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user