Skip to content

Commit

Permalink
Port Login to use redux-toolkit (#2748)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored Nov 1, 2023
1 parent 9f71860 commit 21787af
Show file tree
Hide file tree
Showing 16 changed files with 266 additions and 488 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"recaptcha",
"reportgenerator",
"sched",
"signup",
"sillsdev",
"Sldr",
"subtag",
Expand Down
2 changes: 1 addition & 1 deletion src/components/App/DefaultState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defaultState as goalTimelineState } from "components/GoalTimeline/DefaultState";
import { defaultState as loginState } from "components/Login/Redux/LoginReducer";
import { defaultState as loginState } from "components/Login/Redux/LoginReduxTypes";
import { defaultState as currentProjectState } from "components/Project/ProjectReduxTypes";
import { defaultState as exportProjectState } from "components/ProjectExport/Redux/ExportProjectReduxTypes";
import { defaultState as pronunciationsState } from "components/Pronunciations/Redux/PronunciationsReduxTypes";
Expand Down
8 changes: 4 additions & 4 deletions src/components/Login/LoginPage/LoginComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BannerType } from "api/models";
import { getBannerText } from "backend";
import router from "browserRouter";
import { LoadingButton } from "components/Buttons";
import { LoginStatus } from "components/Login/Redux/LoginReduxTypes";
import { Path } from "types/path";
import { RuntimeConfig } from "types/runtimeConfig";
import theme from "types/theme";
Expand All @@ -34,8 +35,7 @@ export interface LoginDispatchProps {
}

export interface LoginStateProps {
loginAttempt?: boolean;
loginFailure?: boolean;
status: LoginStatus;
}

interface LoginProps
Expand Down Expand Up @@ -173,7 +173,7 @@ export class Login extends Component<LoginProps, LoginState> {
)}

{/* "Failed to log in" */}
{this.props.loginFailure && (
{this.props.status === LoginStatus.Failure && (
<Typography
variant="body2"
style={{ marginTop: 24, marginBottom: 24, color: "red" }}
Expand Down Expand Up @@ -229,7 +229,7 @@ export class Login extends Component<LoginProps, LoginState> {
color: "primary",
}}
disabled={!this.state.isVerified}
loading={this.props.loginAttempt}
loading={this.props.status === LoginStatus.InProgress}
>
{this.props.t("login.login")}
</LoadingButton>
Expand Down
9 changes: 3 additions & 6 deletions src/components/Login/LoginPage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@ import Login, {
LoginStateProps,
} from "components/Login/LoginPage/LoginComponent";
import {
asyncLogin,
asyncLogIn,
logoutAndResetStore,
} from "components/Login/Redux/LoginActions";
import { reset } from "rootActions";
import { StoreState } from "types";
import { StoreStateDispatch } from "types/Redux/actions";

function mapStateToProps(state: StoreState): LoginStateProps {
return {
loginAttempt: state.loginState && state.loginState.loginAttempt,
loginFailure: state.loginState && state.loginState.loginFailure,
};
return { status: state.loginState.loginStatus };
}

function mapDispatchToProps(dispatch: StoreStateDispatch): LoginDispatchProps {
return {
login: (username: string, password: string) => {
dispatch(asyncLogin(username, password));
dispatch(asyncLogIn(username, password));
},
logout: () => {
dispatch(logoutAndResetStore());
Expand Down
5 changes: 4 additions & 1 deletion src/components/Login/LoginPage/tests/LoginComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import "tests/reactI18nextMock";

import Login from "components/Login/LoginPage/LoginComponent";
import { LoginStatus } from "components/Login/Redux/LoginReduxTypes";

jest.mock(
"@matt-block/react-recaptcha-v2",
Expand All @@ -31,7 +32,9 @@ const MOCK_EVENT = { preventDefault: jest.fn(), target: { value: DATA } };
describe("Testing login component", () => {
beforeEach(async () => {
await act(async () => {
loginMaster = create(<Login logout={LOGOUT} reset={LOGOUT} />);
loginMaster = create(
<Login logout={LOGOUT} reset={LOGOUT} status={LoginStatus.Default} />
);
});
loginHandle = loginMaster.root.findByType(Login);
LOGOUT.mockClear();
Expand Down
98 changes: 45 additions & 53 deletions src/components/Login/Redux/LoginActions.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,65 @@
import { PayloadAction } from "@reduxjs/toolkit";
import Hex from "crypto-js/enc-hex";
import sha256 from "crypto-js/sha256";

import * as backend from "backend";
import router from "browserRouter";
import {
LoginActionTypes,
UserAction,
} from "components/Login/Redux/LoginReduxTypes";
setLoginAttemptAction,
setLoginFailureAction,
setLoginSuccessAction,
setSignupAttemptAction,
setSignupFailureAction,
setSignupSuccessAction,
} from "components/Login/Redux/LoginReducer";
import { reset } from "rootActions";
import { StoreStateDispatch } from "types/Redux/actions";
import { Path } from "types/path";
import { newUser } from "types/user";

// thunk action creator
export function asyncLogin(username: string, password: string) {
// Action Creation Functions

export function loginAttempt(username: string): PayloadAction {
return setLoginAttemptAction(username);
}

export function loginFailure(error: string): PayloadAction {
return setLoginFailureAction(error);
}

export function loginSuccess(): PayloadAction {
return setLoginSuccessAction();
}

export function signupAttempt(username: string): PayloadAction {
return setSignupAttemptAction(username);
}

export function signupFailure(error: string): PayloadAction {
return setSignupFailureAction(error);
}

export function signupSuccess(): PayloadAction {
return setSignupSuccessAction();
}

// Dispatch Functions

export function asyncLogIn(username: string, password: string) {
return async (dispatch: StoreStateDispatch) => {
dispatch(loginAttempt(username));
await backend
.authenticateUser(username, password)
.then(async (user) => {
dispatch(loginSuccess(user.username));
dispatch(loginSuccess());
// hash the user name and use it in analytics.identify
const analyticsId = Hex.stringify(sha256(user.id));
analytics.identify(analyticsId);
router.navigate(Path.ProjScreen);
})
.catch(() => dispatch(loginFailure(username)));
};
}

export function loginAttempt(username: string): UserAction {
return {
type: LoginActionTypes.LOGIN_ATTEMPT,
payload: { username },
};
}

export function loginFailure(username: string): UserAction {
return {
type: LoginActionTypes.LOGIN_FAILURE,
payload: { username },
};
}

export function loginSuccess(username: string): UserAction {
return {
type: LoginActionTypes.LOGIN_SUCCESS,
payload: { username },
.catch((err) =>
dispatch(loginFailure(err.response?.data ?? err.message))
);
};
}

Expand All @@ -63,41 +76,20 @@ export function asyncSignUp(
password: string
) {
return async (dispatch: StoreStateDispatch) => {
dispatch(signUpAttempt(username));
dispatch(signupAttempt(username));
// Create new user
const user = newUser(name, username, password);
user.email = email;
await backend
.addUser(user)
.then(() => {
dispatch(signUpSuccess(username));
dispatch(signupSuccess());
setTimeout(() => {
dispatch(asyncLogin(username, password));
dispatch(asyncLogIn(username, password));
}, 1000);
})
.catch((err) =>
dispatch(signUpFailure(err.response?.data ?? err.message))
dispatch(signupFailure(err.response?.data ?? err.message))
);
};
}

export function signUpAttempt(username: string): UserAction {
return {
type: LoginActionTypes.SIGN_UP_ATTEMPT,
payload: { username },
};
}

export function signUpFailure(errorMessage: string): UserAction {
return {
type: LoginActionTypes.SIGN_UP_FAILURE,
payload: { username: errorMessage },
};
}

export function signUpSuccess(username: string): UserAction {
return {
type: LoginActionTypes.SIGN_UP_SUCCESS,
payload: { username },
};
}
117 changes: 49 additions & 68 deletions src/components/Login/Redux/LoginReducer.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,53 @@
import { createSlice } from "@reduxjs/toolkit";

import {
LoginActionTypes,
LoginState,
UserAction,
LoginStatus,
defaultState,
} from "components/Login/Redux/LoginReduxTypes";
import { StoreAction, StoreActionTypes } from "rootActions";
import { StoreActionTypes } from "rootActions";

const loginSlice = createSlice({
name: "loginState",
initialState: defaultState,
reducers: {
setLoginAttemptAction: (state, action) => {
state.error = "";
state.loginStatus = LoginStatus.InProgress;
state.signupStatus = LoginStatus.Default;
state.username = action.payload;
},
setLoginFailureAction: (state, action) => {
state.error = action.payload;
state.loginStatus = LoginStatus.Failure;
},
setLoginSuccessAction: (state) => {
state.loginStatus = LoginStatus.Success;
},
setSignupAttemptAction: (state, action) => {
state.error = "";
state.loginStatus = LoginStatus.Default;
state.signupStatus = LoginStatus.InProgress;
state.username = action.payload;
},
setSignupFailureAction: (state, action) => {
state.error = action.payload;
state.signupStatus = LoginStatus.Failure;
},
setSignupSuccessAction: (state) => {
state.signupStatus = LoginStatus.Success;
},
},
extraReducers: (builder) =>
builder.addCase(StoreActionTypes.RESET, () => defaultState),
});

export const defaultState: LoginState = {
username: "",
loginAttempt: false,
loginFailure: false,
loginSuccess: false,
signUpAttempt: false,
signUpFailure: "",
signUpSuccess: false,
};
export const {
setLoginAttemptAction,
setLoginFailureAction,
setLoginSuccessAction,
setSignupAttemptAction,
setSignupFailureAction,
setSignupSuccessAction,
} = loginSlice.actions;

export const loginReducer = (
state: LoginState = defaultState, //createStore() calls each reducer with undefined state
action: StoreAction | UserAction
): LoginState => {
switch (action.type) {
case LoginActionTypes.LOGIN_ATTEMPT:
return {
...state,
username: action.payload.username,
loginAttempt: true,
loginSuccess: false,
loginFailure: false,
};
case LoginActionTypes.LOGIN_FAILURE:
return {
...state,
username: action.payload.username,
loginAttempt: false,
loginFailure: true,
loginSuccess: false,
};
case LoginActionTypes.LOGIN_SUCCESS:
return {
...state,
username: action.payload.username,
loginSuccess: true,
};
case LoginActionTypes.SIGN_UP_ATTEMPT:
return {
...state,
username: action.payload.username,
signUpAttempt: true,
signUpFailure: "",
signUpSuccess: false,
};
case LoginActionTypes.SIGN_UP_SUCCESS:
return {
...state,
username: action.payload.username,
signUpAttempt: false,
signUpSuccess: true,
};
case LoginActionTypes.SIGN_UP_FAILURE:
return {
...state,
signUpAttempt: false,
signUpFailure: action.payload.username,
signUpSuccess: false,
};
case StoreActionTypes.RESET:
return defaultState;
default:
return state;
}
};
export default loginSlice.reducer;
Loading

0 comments on commit 21787af

Please sign in to comment.