Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/auth/ducks/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const authenticateUser = genericAsyncActions<TokenPayload, any>();
export const logoutUser = genericAsyncActions<void, void>();

export type UserAuthenticationActions =
| ReturnType<typeof authenticateUser.notStarted>
| ReturnType<typeof authenticateUser.loading>
| ReturnType<typeof authenticateUser.loaded>
| ReturnType<typeof authenticateUser.failed>
Expand Down
8 changes: 5 additions & 3 deletions src/auth/ducks/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { TokenPayload, UserAuthenticationReducerState } from './types';
import { authenticateUser } from './actions';
import { C4CAction } from '../../store';
import {
AsyncRequestNotStarted,
ASYNC_REQUEST_FAILED_ACTION,
ASYNC_REQUEST_LOADED_ACTION,
ASYNC_REQUEST_LOADING_ACTION,
AsyncRequestNotStarted,
ASYNC_REQUEST_NOT_STARTED_ACTION,
generateAsyncRequestReducer,
} from '../../utils/asyncRequest';
import { authenticateUser } from './actions';
import { TokenPayload, UserAuthenticationReducerState } from './types';

export const initialUserState: UserAuthenticationReducerState = {
tokens: AsyncRequestNotStarted<TokenPayload, any>(),
Expand All @@ -24,6 +25,7 @@ const reducers = (
action: C4CAction,
): UserAuthenticationReducerState => {
switch (action.type) {
case ASYNC_REQUEST_NOT_STARTED_ACTION:
case ASYNC_REQUEST_LOADING_ACTION:
case ASYNC_REQUEST_LOADED_ACTION:
case ASYNC_REQUEST_FAILED_ACTION:
Expand Down
10 changes: 5 additions & 5 deletions src/auth/ducks/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { C4CState, LOCALSTORAGE_STATE_KEY } from '../../store';
import { asyncRequestIsComplete } from '../../utils/asyncRequest';
import AppAxiosInstance from '../axios';
import { authenticateUser, logoutUser } from './actions';
import {
LoginRequest,
SignupRequest,
TokenPayload,
UserAuthenticationThunkAction,
} from './types';
import { authenticateUser, logoutUser } from './actions';
import { C4CState, LOCALSTORAGE_STATE_KEY } from '../../store';
import { asyncRequestIsComplete } from '../../utils/asyncRequest';
import AppAxiosInstance from '../axios';

export const login = (
loginRequest: LoginRequest,
Expand Down Expand Up @@ -48,7 +48,7 @@ export const signup = (
export const logout = (): UserAuthenticationThunkAction<void> => {
return (dispatch, getState, { authClient }): Promise<void> => {
localStorage.removeItem(LOCALSTORAGE_STATE_KEY);

dispatch(authenticateUser.notStarted());
const state: C4CState = getState();

if (asyncRequestIsComplete(state.authenticationState.tokens)) {
Expand Down
16 changes: 16 additions & 0 deletions src/auth/test/reducers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,20 @@ describe('User Authentication Reducers', () => {

expect(reducers(initialUserState, action)).toEqual(expectedNextState);
});

it('Clears tokens correctly when setting state to NotStarted', () => {
const payload: TokenPayload = {
accessToken:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjNGMiLCJleHAiOjE2MDQ4NzIwODIsInVzZXJuYW1lIjoiamFja2JsYW5jIn0.k0D1rySdVqVatWsjdA4i1YYq-7glzrL3ycSQwz-5zLU',
refreshToken:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjNGMiLCJleHAiOjE2MDU0NzUwODIsInVzZXJuYW1lIjoiamFja2JsYW5jIn0.FHgEdtz16H5u7mtTqE81N4PUsnzjvwdaJ4GK_jdLWAY',
};
const action = authenticateUser.notStarted();
const authenticatedState: UserAuthenticationReducerState = {
...initialUserState,
tokens: AsyncRequestCompleted<TokenPayload, void>(payload),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically it's an any error right?

};

expect(reducers(authenticatedState, action)).toEqual(initialUserState);
});
});
27 changes: 25 additions & 2 deletions src/utils/asyncRequest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Action } from '../store';
import { Reducer } from 'redux';
import { v4 } from 'uuid';
import { Action } from '../store';

export enum AsyncRequestKinds {
NotStarted = 'NotStarted',
Expand Down Expand Up @@ -152,10 +152,14 @@ export function rehydrateAsyncRequest<R, E = void>(
return request;
}
}

export const ASYNC_REQUEST_NOT_STARTED_ACTION = 'asyncNotStarted';
export const ASYNC_REQUEST_LOADING_ACTION = 'asyncLoading';
export const ASYNC_REQUEST_LOADED_ACTION = 'asyncLoaded';
export const ASYNC_REQUEST_FAILED_ACTION = 'asyncFailed';

interface NotStartedPayload {
readonly key: string;
}
interface LoadingPayload {
readonly key: string;
}
Expand All @@ -169,11 +173,16 @@ interface FailedPayload<E> {
}

export type AsyncRequestAction<R, E> =
| Action<typeof ASYNC_REQUEST_NOT_STARTED_ACTION, NotStartedPayload>
| Action<typeof ASYNC_REQUEST_LOADING_ACTION, LoadingPayload>
| Action<typeof ASYNC_REQUEST_LOADED_ACTION, LoadedPayload<R>>
| Action<typeof ASYNC_REQUEST_FAILED_ACTION, FailedPayload<E>>;

export function genericAsyncActions<R, E>(): {
notStarted: () => Action<
typeof ASYNC_REQUEST_NOT_STARTED_ACTION,
NotStartedPayload
>;
loading: () => Action<typeof ASYNC_REQUEST_LOADING_ACTION, LoadingPayload>;
loaded: (
r: R,
Expand All @@ -185,6 +194,14 @@ export function genericAsyncActions<R, E>(): {
} {
const key = v4(); // UUID4

const notStarted = (): Action<
typeof ASYNC_REQUEST_NOT_STARTED_ACTION,
NotStartedPayload
> => ({
type: ASYNC_REQUEST_NOT_STARTED_ACTION,
payload: { key },
});

const loading = (): Action<
typeof ASYNC_REQUEST_LOADING_ACTION,
LoadingPayload
Expand All @@ -208,6 +225,7 @@ export function genericAsyncActions<R, E>(): {
});

return {
notStarted,
loading,
loaded,
failed,
Expand All @@ -224,6 +242,11 @@ export function generateAsyncRequestReducer<S, R, E>(key: string) {
action: AsyncRequestAction<any, any>,
) => {
switch (action.type) {
case ASYNC_REQUEST_NOT_STARTED_ACTION:
if (action.payload.key === key) {
return AsyncRequestNotStarted<R, E>();
}
break;
case ASYNC_REQUEST_LOADING_ACTION:
if (action.payload.key === key) {
return AsyncRequestLoading<R, E>();
Expand Down
11 changes: 11 additions & 0 deletions src/utils/test/asyncRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AsyncRequestFailed,
AsyncRequestCompleted,
rehydrateAsyncRequest,
ASYNC_REQUEST_NOT_STARTED_ACTION,
} from '../asyncRequest';
import {
PrivilegeLevel,
Expand All @@ -34,6 +35,10 @@ describe('asyncRequest ', () => {
const err = new Error();
const response = 'myResponse';

expect(generator1.notStarted()).toEqual({
type: ASYNC_REQUEST_NOT_STARTED_ACTION,
payload: { key: generator1.key },
});
expect(generator1.loading()).toEqual({
type: ASYNC_REQUEST_LOADING_ACTION,
payload: { key: generator1.key },
Expand All @@ -58,6 +63,12 @@ describe('asyncRequest ', () => {
Error
>(actions.key);

it('updates the state for a not started action with given key', () => {
expect(reducer(initialState, actions.notStarted())).toEqual(
AsyncRequestNotStarted<TokenPayload, Error>(),
);
});

it('updates the state for a loading action with given key', () => {
expect(reducer(initialState, actions.loading())).toEqual(
AsyncRequestLoading<TokenPayload, Error>(),
Expand Down