refactor(typings): migrate 'auth' state slice to TypeScript (#4698)
This commit is contained in:
parent
5aec65fed5
commit
929ca380bb
@ -1,5 +1,9 @@
|
|||||||
import { actions as notifActions } from 'redux-notifications';
|
import { actions as notifActions } from 'redux-notifications';
|
||||||
import { currentBackend } from 'coreSrc/backend';
|
import { Credentials, User } from 'netlify-cms-lib-util';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
import { currentBackend } from '../backend';
|
||||||
|
import { State } from '../types/redux';
|
||||||
|
|
||||||
const { notifSend, notifClear } = notifActions;
|
const { notifSend, notifClear } = notifActions;
|
||||||
|
|
||||||
@ -13,50 +17,49 @@ export const LOGOUT = 'LOGOUT';
|
|||||||
export function authenticating() {
|
export function authenticating() {
|
||||||
return {
|
return {
|
||||||
type: AUTH_REQUEST,
|
type: AUTH_REQUEST,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authenticate(userData) {
|
export function authenticate(userData: User) {
|
||||||
return {
|
return {
|
||||||
type: AUTH_SUCCESS,
|
type: AUTH_SUCCESS,
|
||||||
payload: userData,
|
payload: userData,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authError(error) {
|
export function authError(error: Error) {
|
||||||
return {
|
return {
|
||||||
type: AUTH_FAILURE,
|
type: AUTH_FAILURE,
|
||||||
error: 'Failed to authenticate',
|
error: 'Failed to authenticate',
|
||||||
payload: error,
|
payload: error,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doneAuthenticating() {
|
export function doneAuthenticating() {
|
||||||
return {
|
return {
|
||||||
type: AUTH_REQUEST_DONE,
|
type: AUTH_REQUEST_DONE,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useOpenAuthoring() {
|
export function useOpenAuthoring() {
|
||||||
return {
|
return {
|
||||||
type: USE_OPEN_AUTHORING,
|
type: USE_OPEN_AUTHORING,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return {
|
return {
|
||||||
type: LOGOUT,
|
type: LOGOUT,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user data token is cached and is valid
|
// Check if user data token is cached and is valid
|
||||||
export function authenticateUser() {
|
export function authenticateUser() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
dispatch(authenticating());
|
dispatch(authenticating());
|
||||||
return backend
|
return Promise.resolve(backend.currentUser())
|
||||||
.currentUser()
|
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.useOpenAuthoring) {
|
if (user.useOpenAuthoring) {
|
||||||
@ -67,15 +70,15 @@ export function authenticateUser() {
|
|||||||
dispatch(doneAuthenticating());
|
dispatch(doneAuthenticating());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error: Error) => {
|
||||||
dispatch(authError(error));
|
dispatch(authError(error));
|
||||||
dispatch(logoutUser());
|
dispatch(logoutUser());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loginUser(credentials) {
|
export function loginUser(credentials: Credentials) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ export function loginUser(credentials) {
|
|||||||
}
|
}
|
||||||
dispatch(authenticate(user));
|
dispatch(authenticate(user));
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error: Error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
dispatch(
|
dispatch(
|
||||||
notifSend({
|
notifSend({
|
||||||
@ -106,7 +109,7 @@ export function loginUser(credentials) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logoutUser() {
|
export function logoutUser() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
Promise.resolve(backend.logout()).then(() => {
|
Promise.resolve(backend.logout()).then(() => {
|
||||||
@ -115,3 +118,11 @@ export function logoutUser() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthAction = ReturnType<
|
||||||
|
| typeof authenticating
|
||||||
|
| typeof authenticate
|
||||||
|
| typeof authError
|
||||||
|
| typeof doneAuthenticating
|
||||||
|
| typeof logout
|
||||||
|
>;
|
@ -1,33 +0,0 @@
|
|||||||
import Immutable from 'immutable';
|
|
||||||
import { authenticating, authenticate, authError, logout } from 'Actions/auth';
|
|
||||||
import auth from '../auth';
|
|
||||||
|
|
||||||
describe('auth', () => {
|
|
||||||
it('should handle an empty state', () => {
|
|
||||||
expect(auth(undefined, {})).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an authentication request', () => {
|
|
||||||
expect(auth(undefined, authenticating())).toEqual(Immutable.Map({ isFetching: true }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle authentication', () => {
|
|
||||||
expect(auth(undefined, authenticate({ email: 'joe@example.com' }))).toEqual(
|
|
||||||
Immutable.fromJS({ user: { email: 'joe@example.com' } }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an authentication error', () => {
|
|
||||||
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual(
|
|
||||||
Immutable.Map({
|
|
||||||
error: 'Error: Bad credentials',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle logout', () => {
|
|
||||||
const initialState = Immutable.fromJS({ user: { email: 'joe@example.com' } });
|
|
||||||
const newState = auth(initialState, logout());
|
|
||||||
expect(newState.get('user')).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,32 @@
|
|||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import { authenticating, authenticate, authError, logout } from '../../actions/auth';
|
||||||
|
import auth, { defaultState } from '../auth';
|
||||||
|
|
||||||
|
describe('auth', () => {
|
||||||
|
it('should handle an empty state', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||||
|
// @ts-ignore
|
||||||
|
expect(auth(undefined, {})).toEqual(defaultState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an authentication request', () => {
|
||||||
|
expect(auth(undefined, authenticating())).toEqual(defaultState.set('isFetching', true));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle authentication', () => {
|
||||||
|
const user = { name: 'joe', token: 'token' };
|
||||||
|
expect(auth(undefined, authenticate(user))).toEqual(defaultState.set('user', fromJS(user)));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an authentication error', () => {
|
||||||
|
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual(
|
||||||
|
defaultState.set('error', 'Error: Bad credentials'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle logout', () => {
|
||||||
|
const user = { name: 'joe', token: 'token' };
|
||||||
|
const newState = auth(defaultState.set('user', fromJS(user)), logout());
|
||||||
|
expect(newState.get('user')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
@ -1,21 +0,0 @@
|
|||||||
import Immutable from 'immutable';
|
|
||||||
import { AUTH_REQUEST, AUTH_SUCCESS, AUTH_FAILURE, AUTH_REQUEST_DONE, LOGOUT } from 'Actions/auth';
|
|
||||||
|
|
||||||
const auth = (state = null, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case AUTH_REQUEST:
|
|
||||||
return Immutable.Map({ isFetching: true });
|
|
||||||
case AUTH_SUCCESS:
|
|
||||||
return Immutable.fromJS({ user: action.payload });
|
|
||||||
case AUTH_FAILURE:
|
|
||||||
return Immutable.Map({ error: action.payload && action.payload.toString() });
|
|
||||||
case AUTH_REQUEST_DONE:
|
|
||||||
return state.remove('isFetching');
|
|
||||||
case LOGOUT:
|
|
||||||
return state.remove('user').remove('isFetching');
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default auth;
|
|
42
packages/netlify-cms-core/src/reducers/auth.ts
Normal file
42
packages/netlify-cms-core/src/reducers/auth.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import { User } from 'netlify-cms-lib-util';
|
||||||
|
import {
|
||||||
|
AUTH_REQUEST,
|
||||||
|
AUTH_SUCCESS,
|
||||||
|
AUTH_FAILURE,
|
||||||
|
AUTH_REQUEST_DONE,
|
||||||
|
LOGOUT,
|
||||||
|
AuthAction,
|
||||||
|
} from '../actions/auth';
|
||||||
|
import { StaticallyTypedRecord } from '../types/immutable';
|
||||||
|
|
||||||
|
export type Auth = StaticallyTypedRecord<{
|
||||||
|
isFetching: boolean;
|
||||||
|
user: StaticallyTypedRecord<User> | undefined;
|
||||||
|
error: string | undefined;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const defaultState = fromJS({
|
||||||
|
isFetching: false,
|
||||||
|
user: undefined,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = (state = defaultState as Auth, 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default auth;
|
@ -3,6 +3,7 @@ import { StaticallyTypedRecord } from './immutable';
|
|||||||
import { Map, List, OrderedMap, Set } from 'immutable';
|
import { Map, List, OrderedMap, Set } from 'immutable';
|
||||||
import AssetProxy from '../valueObjects/AssetProxy';
|
import AssetProxy from '../valueObjects/AssetProxy';
|
||||||
import { MediaFile as BackendMediaFile } from '../backend';
|
import { MediaFile as BackendMediaFile } from '../backend';
|
||||||
|
import { Auth } from '../reducers/auth';
|
||||||
|
|
||||||
export type SlugConfig = StaticallyTypedRecord<{
|
export type SlugConfig = StaticallyTypedRecord<{
|
||||||
encoding: string;
|
encoding: string;
|
||||||
@ -295,6 +296,7 @@ export type Status = StaticallyTypedRecord<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
|
auth: Auth;
|
||||||
config: Config;
|
config: Config;
|
||||||
cursors: Cursors;
|
cursors: Cursors;
|
||||||
collections: Collections;
|
collections: Collections;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user