Skip to content

Commit

Permalink
feat: persistance of login and singup forms
Browse files Browse the repository at this point in the history
Description:
Persistance of login and signup forms
VAN-1957
  • Loading branch information
Ahtesham Quraish authored and Ahtesham Quraish committed Jul 11, 2024
1 parent ac0442f commit fcbfffa
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 24 deletions.
12 changes: 12 additions & 0 deletions src/forms/login-popup/data/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export const loginInitialState = {
isLoginSSOIntent: false,
loginError: {},
loginResult: {},
loginFormData: {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
},
showResetPasswordSuccessBanner: false,
};

Expand Down Expand Up @@ -59,6 +67,9 @@ export const loginSlice = createSlice({
setLoginSSOIntent: (state) => {
state.isLoginSSOIntent = true;
},
backupLoginFormBegin: (state, { payload }) => {
state.loginFormData = payload;
},
},
});

Expand All @@ -69,6 +80,7 @@ export const {
loginUserFailed,
setShowPasswordResetBanner,
setLoginSSOIntent,
backupLoginFormBegin,
} = loginSlice.actions;

export default loginSlice.reducer;
24 changes: 15 additions & 9 deletions src/forms/login-popup/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import AccountActivationMessage from './components/AccountActivationMessage';
import LoginFailureAlert from './components/LoginFailureAlert';
import { NUDGE_PASSWORD_CHANGE, REQUIRE_PASSWORD_CHANGE } from './data/constants';
import useGetActivationMessage from './data/hooks';
import { loginUser, setLoginSSOIntent } from './data/reducers';
import {
backupLoginFormBegin, loginErrorClear, loginUser, setLoginSSOIntent,
} from './data/reducers';
import messages from './messages';
import { InlineLink, SocialAuthProviders } from '../../common-ui';
import {
Expand Down Expand Up @@ -58,6 +60,7 @@ const LoginForm = () => {
const isEditingFieldRef = useRef(false);

const loginResult = useSelector(state => state.login.loginResult);
const backedUpFormData = useSelector(state => state.login.loginFormData);
const loginErrorCode = useSelector(state => state.login.loginError?.errorCode);
const loginErrorContext = useSelector(state => state.login.loginError?.errorContext);
const providers = useSelector(state => state.commonData.thirdPartyAuthContext?.providers);
Expand All @@ -70,15 +73,9 @@ const LoginForm = () => {

const accountActivation = useGetActivationMessage();

const [formFields, setFormFields] = useState({
emailOrUsername: '',
password: '',
});
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });

const [formErrors, setFormErrors] = useState({
emailOrUsername: '',
password: '',
});
const [formErrors, setFormErrors] = useState({ ...backedUpFormData.errors });
const [errorCode, setErrorCode] = useState({ type: '', context: {} });

useEffect(() => {
Expand Down Expand Up @@ -193,6 +190,13 @@ const LoginForm = () => {
trackForgotPasswordLinkClick();
};

const backupFormDataHandler = () => {
dispatch(backupLoginFormBegin({
formFields: { ...formFields },
errors: { ...formErrors },
}));
};

const handleSubmit = (e) => {
e.preventDefault();

Expand Down Expand Up @@ -303,6 +307,8 @@ const LoginForm = () => {
className="mb-2"
onClick={() => {
trackRegisterFormToggled();
backupFormDataHandler();
dispatch(loginErrorClear());
dispatch(setCurrentOpenedForm(REGISTRATION_FORM));
}}
linkHelpText={formatMessage(messages.loginFormRegistrationHelpText)}
Expand Down
26 changes: 25 additions & 1 deletion src/forms/login-popup/tests/LoginPopup.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { OnboardingComponentContext } from '../../../data/storeHooks';
import getAllPossibleQueryParams from '../../../data/utils';
import { setCurrentOpenedForm } from '../../../onboarding-component/data/reducers';
import { NUDGE_PASSWORD_CHANGE, REQUIRE_PASSWORD_CHANGE } from '../data/constants';
import { loginUser } from '../data/reducers';
import { backupLoginFormBegin, loginUser } from '../data/reducers';
import LoginForm from '../index';

const IntlLoginForm = injectIntl(LoginForm);
Expand All @@ -33,6 +33,15 @@ jest.mock('../../../tracking/trackers/login', () => ({
describe('LoginForm Test', () => {
let store = {};

const loginFormData = {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
};

const reduxWrapper = children => (
<IntlProvider locale="en">
<MemoryRouter>
Expand All @@ -45,6 +54,7 @@ describe('LoginForm Test', () => {
login: {
submitState: DEFAULT_STATE,
loginResult: { success: false, redirectUrl: '' },
loginFormData,
loginError: {},
},
register: {
Expand Down Expand Up @@ -246,6 +256,20 @@ describe('LoginForm Test', () => {
expect(store.dispatch).toHaveBeenCalledWith(setCurrentOpenedForm(REGISTRATION_FORM));
});

it('should backup the login form state when switch to sign-up form', () => {
store = mockStore({
...initialState,
login: {
...initialState.login,
},
});

store.dispatch = jest.fn(store.dispatch);
const { getByText } = render(reduxWrapper(<IntlLoginForm />));
fireEvent.click(getByText('Create account'));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin({ ...loginFormData }));
});

it('should dispatch setCurrentOpenedForm action on "Forgot Password?" link click', () => {
store.dispatch = jest.fn(store.dispatch);
const { getByText } = render(reduxWrapper(<IntlLoginForm />));
Expand Down
13 changes: 13 additions & 0 deletions src/forms/registration-popup/data/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ export const registerInitialState = {
validationState: DEFAULT_STATE,
registrationError: {},
registrationResult: {},
registrationFormData: {
isFormDirty: false,
formFields: {
name: '', email: '', password: '', marketingEmailsOptIn: true,
},
errors: {
name: '', email: '', password: '',
},
},
registrationFields: { marketingEmailsOptIn: true },
userPipelineDataLoaded: false,
validationApiRateLimited: false,
Expand Down Expand Up @@ -65,6 +74,9 @@ export const registerSlice = createSlice({
setRegistrationFields: (state, { payload }) => {
state.registrationFields = payload;
},
backupRegistrationFormBegin: (state, { payload }) => {
state.registrationFormData = payload;
},
},
});

Expand All @@ -78,6 +90,7 @@ export const {
fetchRealtimeValidationsFailed,
clearAllRegistrationErrors,
clearRegistrationBackendError,
backupRegistrationFormBegin,
} = registerSlice.actions;

export default registerSlice.reducer;
27 changes: 19 additions & 8 deletions src/forms/registration-popup/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import classNames from 'classnames';
import HonorCodeAndPrivacyPolicyMessage from './components/honorCodeAndTOS';
import RegistrationFailureAlert from './components/RegistrationFailureAlert';
import {
backupRegistrationFormBegin,
clearAllRegistrationErrors,
clearRegistrationBackendError,
registerUser,
Expand Down Expand Up @@ -63,13 +64,6 @@ const RegistrationForm = () => {

const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth - 1 });

const [formFields, setFormFields] = useState({
name: '', email: '', password: '', marketingEmailsOptIn: true,
});
const [errors, setErrors] = useState({});
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
const [userPipelineDataLoaded, setUserPipelineDataLoaded] = useState(false);

const emailRef = useRef(null);
const registerErrorAlertRef = useRef(null);
const socialAuthButtonRef = useRef(null);
Expand All @@ -80,6 +74,8 @@ const RegistrationForm = () => {

const registrationResult = useSelector(state => state.register.registrationResult);

const backedUpFormData = useSelector(state => state.register.registrationFormData);

const onboardingComponentContext = useSelector(state => state.commonData.onboardingComponentContext);
const thirdPartyAuthApiStatus = useSelector(state => state.commonData.thirdPartyAuthApiStatus);
const thirdPartyAuthErrorMessage = useSelector(state => state.commonData.thirdPartyAuthContext.errorMessage);
Expand All @@ -94,6 +90,11 @@ const RegistrationForm = () => {
const backendValidations = useSelector(getBackendValidations);
const submitState = useSelector(state => state.register.submitState);

const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
const [userPipelineDataLoaded, setUserPipelineDataLoaded] = useState(false);

const autoSubmitRegForm = (currentProvider
&& thirdPartyAuthApiStatus === COMPLETE_STATE
&& !isLoginSSOIntent
Expand All @@ -111,7 +112,7 @@ const RegistrationForm = () => {
localStorage.removeItem('marketingEmailsOptIn');
localStorage.removeItem('ssoPipelineRedirectionDone');
}
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) {
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0 && !backedUpFormData.isFormDirty) {
const {
name = '', email = '',
} = pipelineUserDetails;
Expand Down Expand Up @@ -220,6 +221,15 @@ const RegistrationForm = () => {
}));
};

const backupFormDataHandler = () => {
dispatch(backupRegistrationFormBegin({
...backedUpFormData,
isFormDirty: true,
formFields: { ...formFields },
errors: { ...errors },
}));
};

const handleUserRegistration = () => {
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
const userCountryCode = getCountryCookieValue();
Expand Down Expand Up @@ -407,6 +417,7 @@ const RegistrationForm = () => {
className="mb-2"
onClick={() => {
trackLoginFormToggled();
backupFormDataHandler();
dispatch(clearAllRegistrationErrors());
dispatch(setCurrentOpenedForm(LOGIN_FORM));
}}
Expand Down
26 changes: 25 additions & 1 deletion src/forms/registration-popup/tests/RegistrationPopup.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '../../../data/constants';
import { OnboardingComponentContext } from '../../../data/storeHooks';
import { setCurrentOpenedForm } from '../../../onboarding-component/data/reducers';
import { clearRegistrationBackendError, registerUser } from '../data/reducers';
import { backupRegistrationFormBegin, clearRegistrationBackendError, registerUser } from '../data/reducers';
import * as utils from '../data/utils';
import RegistrationForm from '../index';

Expand Down Expand Up @@ -44,6 +44,15 @@ const populateRequiredFields = (
describe('RegistrationForm Test', () => {
let store = {};

const registrationFormData = {
formFields: {
name: '', email: '', password: '', marketingEmailsOptIn: true,
},
errors: {
name: '', email: '', password: '',
},
};

const reduxWrapper = children => (
<IntlProvider locale="en">
<MemoryRouter>
Expand All @@ -57,6 +66,7 @@ describe('RegistrationForm Test', () => {
submitState: DEFAULT_STATE,
registrationError: {},
registrationResult: {},
registrationFormData: { ...registrationFormData },
},
login: {
isLoginSSOIntent: false,
Expand Down Expand Up @@ -183,6 +193,20 @@ describe('RegistrationForm Test', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(registerUser({}));
});

it('should backup the registration form state when switch to login form', () => {
store = mockStore({
...initialState,
register: {
...initialState.register,
},
});

store.dispatch = jest.fn(store.dispatch);
const { getByText } = render(reduxWrapper(<IntlRegistrationForm />));
fireEvent.click(getByText('Sign In'));
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationFormBegin({ ...registrationFormData }));
});

// ******** test registration form validations ********

it('should show error messages for required fields on empty form submission', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const FORGOT_PASSWORD_SLICE_NAME = 'forgotPassword';

export const forgotPasswordInitialState = {
status: DEFAULT_STATE,
forgotPasswordFormData: {
email: '',
error: '',
},
};

export const forgotPasswordSlice = createSlice({
Expand All @@ -43,6 +47,9 @@ export const forgotPasswordSlice = createSlice({
forgotPassweordTokenInvalidFailure: (state, { payload }) => {
state.status = payload;
},
setForgotPasswordFormData: (state, { payload }) => {
state.forgotPasswordFormData = payload;
},
},
});

Expand All @@ -53,6 +60,7 @@ export const {
forgotPasswordFailed,
forgotPasswordClearStatus,
forgotPassweordTokenInvalidFailure,
setForgotPasswordFormData,
} = forgotPasswordSlice.actions;

export default forgotPasswordSlice.reducer;
16 changes: 12 additions & 4 deletions src/forms/reset-password-popup/forgot-password/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Button, Container, Form, StatefulButton,
} from '@openedx/paragon';

import { forgotPassword, forgotPasswordClearStatus } from './data/reducers';
import { forgotPassword, forgotPasswordClearStatus, setForgotPasswordFormData } from './data/reducers';
import getValidationMessage from './data/utils';
import ForgotPasswordFailureAlert from './ForgotPasswordFailureAlert';
import ForgotPasswordSuccess from './ForgotPasswordSuccess';
Expand All @@ -25,11 +25,13 @@ import '../index.scss';
const ForgotPasswordForm = () => {
const { formatMessage } = useIntl();
const dispatch = useDispatch();

const status = useSelector(state => state.forgotPassword?.status);
const backedUpFormData = useSelector(state => state.forgotPassword?.forgotPasswordFormData);
const loginErrorCode = useSelector(state => state.login.loginError?.errorCode);

const [formErrors, setFormErrors] = useState('');
const [formFields, setFormFields] = useState({ email: '' });
const [formErrors, setFormErrors] = useState(backedUpFormData.error);
const [formFields, setFormFields] = useState({ email: backedUpFormData.email });
const [isSuccess, setIsSuccess] = useState(false);

const emailRef = useRef(null);
Expand All @@ -49,9 +51,15 @@ const ForgotPasswordForm = () => {
const handleErrorChange = (fieldName, error) => {
setFormErrors(error);
};

const backupFormDataHandler = () => {
dispatch(setForgotPasswordFormData({
email: formFields.email,
error: formErrors,
}));
};
const backToLogin = (e) => {
e.preventDefault();
backupFormDataHandler();
dispatch(forgotPasswordClearStatus());
dispatch(loginErrorClear());
dispatch(setCurrentOpenedForm(LOGIN_FORM));
Expand Down
Loading

0 comments on commit fcbfffa

Please sign in to comment.