Skip to content

Commit

Permalink
fix: progressive profiling and registration (#80)
Browse files Browse the repository at this point in the history
- There should be no error message on undetected option in country field bydefault
- cookie value should be on variable base
- check user country on progressive profiling popup, if not set not allow user to skip or register
- pass country in register payload on the base of country cookie
  • Loading branch information
mubbsharanwar authored Jun 24, 2024
1 parent 14d4a3d commit 58032c1
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 29 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ LOGOUT_URL=http://localhost:18000/login
REFRESH_ACCESS_TOKEN_ENDPOINT=http://localhost:18000/login_refresh
SITE_NAME=edX
INFO_EMAIL=[email protected]
ONBOARDING_COMPONENT_ENV='development'
# ***** Algolia keys to fetch subject list *****
AUTHN_ALGOLIA_APP_ID=''
AUTHN_ALGOLIA_SEARCH_API_KEY=''
Expand Down
1 change: 1 addition & 0 deletions example/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ initialize({
PRIVACY_POLICY: process.env.PRIVACY_POLICY || '',
INFO_EMAIL: process.env.INFO_EMAIL || '',
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
ONBOARDING_COMPONENT_ENV: process.env.ONBOARDING_COMPONENT_ENV || '',
});
},
},
Expand Down
9 changes: 0 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"core-js": "3.36.0",
"fastest-levenshtein": "^1.0.16",
"form-urlencoded": "^6.1.5",
"js-cookie": "^3.0.5",
"query-string": "^7.1.3",
"react-redux": "7.2.9",
"react-router": "6.22.3",
Expand Down
27 changes: 26 additions & 1 deletion src/data/tests/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import getAllPossibleQueryParams from '../utils';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import Cookies from 'universal-cookie';

import getAllPossibleQueryParams, { getCountryCookieValue } from '../utils';

describe('getAllPossibleQueryParams', () => {
beforeEach(() => {
Expand All @@ -24,3 +27,25 @@ describe('getAllPossibleQueryParams', () => {
expect(queryParams).toEqual({ course_id: 'value1', next: 'value2' });
});
});

describe('getCountryCookieValue', () => {
// Mock Cookies
jest.mock('universal-cookie');

mergeConfig({ ONBOARDING_COMPONENT_ENV: 'development' });

it('should return cookie value', () => {
const cookie = new Cookies();
cookie.set(`${getConfig().ONBOARDING_COMPONENT_ENV}-edx-cf-loc`, 'US');
const countryCode = getCountryCookieValue();
expect(countryCode).toEqual('US');
});

it('should return undefined cookie value', () => {
const cookie = new Cookies();
cookie.remove(`${getConfig().ONBOARDING_COMPONENT_ENV}-edx-cf-loc`);

const countryCode = getCountryCookieValue();
expect(countryCode).toEqual(undefined);
});
});
6 changes: 6 additions & 0 deletions src/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ export const setCookie = (cookieName, cookieValue, cookieExpiry) => {
}
};

export const getCountryCookieValue = () => {
const cookieName = `${getConfig().ONBOARDING_COMPONENT_ENV}-edx-cf-loc`;
const cookies = new Cookies();
return cookies.get(cookieName);
};

export default getAllPossibleQueryParams;
4 changes: 0 additions & 4 deletions src/forms/progressive-profiling-popup/data/utils.js

This file was deleted.

27 changes: 15 additions & 12 deletions src/forms/progressive-profiling-popup/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { Language } from '@openedx/paragon/icons';
import { extendedProfileFields, optionalFieldsData } from './data/constants';
import useSubjectsList from './data/hooks/useSubjectList';
import { saveUserProfile } from './data/reducers';
import languageCookieValue from './data/utils';
import messages from './messages';
import { setCurrentOpenedForm } from '../../authn-component/data/reducers';
import { COMPLETE_STATE, LOGIN_FORM } from '../../data/constants';
import { useDispatch, useSelector } from '../../data/storeHooks';
import { getCountryCookieValue } from '../../data/utils';
import {
trackProgressiveProfilingPageEvent,
trackProgressiveProfilingSkipLinkClickEvent,
Expand All @@ -39,15 +39,14 @@ const ProgressiveProfilingForm = () => {
const { formatMessage } = useIntl();
const dispatch = useDispatch();

const countryCookieValue = getCountryCookieValue();
const { subjectsList, subjectsLoading } = useSubjectsList();
const countryList = useMemo(() => getCountryList(getLocale()), []);

const submitState = useSelector(state => state.progressiveProfiling.submitState);
const redirectUrl = useSelector(state => state.progressiveProfiling.redirectUrl);

const authContextCountryCode = useSelector(state => state.commonData.thirdPartyAuthContext.countryCode);
const finishAuthUrl = useSelector(state => state.commonData.thirdPartyAuthContext.finishAuthUrl);

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

const [formData, setFormData] = useState({});
Expand All @@ -56,14 +55,13 @@ const ProgressiveProfilingForm = () => {

useEffect(() => {
let countryCode = null;
if (languageCookieValue) {
countryCode = languageCookieValue;
} else {
if (countryCookieValue) {
countryCode = countryCookieValue;
} else if (authContextCountryCode) {
countryCode = authContextCountryCode;
}

if (!countryCode) {
setFormErrors({ country: formatMessage(messages.progressiveProfilingCountryFieldErrorMessage) });
return;
}
const userCountry = countryList.find((country) => country.code === countryCode);
Expand All @@ -72,7 +70,7 @@ const ProgressiveProfilingForm = () => {
// set formData state for auto populated country field to pass into payload
setFormData({ country: userCountry?.code });
}
}, [authContextCountryCode, autoFilledCountry, countryList, formatMessage]);
}, [authContextCountryCode, autoFilledCountry, countryCookieValue, countryList, formatMessage]);

useEffect(() => {
if (authenticatedUser === null) {
Expand Down Expand Up @@ -159,11 +157,16 @@ const ProgressiveProfilingForm = () => {
const handleSkip = (e) => {
e.preventDefault();

if (hasFormErrors()) {
return;
const hasCountry = !!countryCookieValue || !!authContextCountryCode;
if (hasFormErrors() && !hasCountry) {
setFormErrors({ ...formErrors, country: formatMessage(messages.progressiveProfilingCountryFieldErrorMessage) });
} else if (!hasFormErrors() && !hasCountry) {
// TODO update error message copy here if user has no country value set in the backend and wants to skip this form
setFormErrors({ ...formErrors, country: formatMessage(messages.progressiveProfilingCountryFieldErrorMessage) });
} else if (hasCountry) {
trackProgressiveProfilingSkipLinkClickEvent();
window.location.href = redirectUrl;
}
trackProgressiveProfilingSkipLinkClickEvent();
window.location.href = redirectUrl;
};

return (
Expand Down
28 changes: 27 additions & 1 deletion src/forms/progressive-profiling-popup/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,14 @@ describe('ProgressiveProfilingForm Test', () => {
expect(countryInput.value).toEqual('United States of America');
});

it('should redirect to redirect url on skip button click', () => {
it('should redirect to redirect url on skip button click if user profile has country field', () => {
store = mockStore({
...initialState,
commonData: {
thirdPartyAuthContext: {
countryCode: 'US',
},
},
progressiveProfiling: {
redirectUrl: 'http://example.com',
},
Expand All @@ -272,6 +277,27 @@ describe('ProgressiveProfilingForm Test', () => {
expect(window.location.href).toEqual('http://example.com');
});

it('should not redirect to redirect url on skip button click if country not set in user profile', () => {
store = mockStore({
...initialState,
progressiveProfiling: {
redirectUrl: 'http://example.com',
},
});

delete window.location;
window.location = {
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
href: getConfig().LMS_BASE_URL,
};
const { container } = render(reduxWrapper(<IntlProgressiveProfilingForm />));

const skipButton = container.querySelector('#skip-optional-fields');
fireEvent.click(skipButton);

expect(window.location.href).not.toEqual('http://example.com');
});

it('should not redirect on skip button click if country not selected', () => {
store = mockStore({
...initialState,
Expand Down
11 changes: 10 additions & 1 deletion src/forms/registration-popup/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
} from '../../data/constants';
import { useDispatch, useSelector } from '../../data/storeHooks';
import './index.scss';
import getAllPossibleQueryParams, { setCookie } from '../../data/utils';
import getAllPossibleQueryParams, { getCountryCookieValue, setCookie } from '../../data/utils';
import { registrationSuccessEvent, trackRegistrationPageEvent } from '../../tracking/trackers/register';
import AuthenticatedRedirection from '../common-components/AuthenticatedRedirection';
import SSOFailureAlert from '../common-components/SSOFailureAlert';
Expand Down Expand Up @@ -73,6 +73,7 @@ const RegistrationForm = () => {
const providers = useSelector(state => state.commonData.thirdPartyAuthContext?.providers);
const currentProvider = useSelector(state => state.commonData.thirdPartyAuthContext.currentProvider);
const pipelineUserDetails = useSelector(state => state.commonData.thirdPartyAuthContext.pipelineUserDetails);
const authContextCountryCode = useSelector(state => state.commonData.thirdPartyAuthContext.countryCode);
const registrationError = useSelector(state => state.register.registrationError);
const isLoginSSOIntent = useSelector(state => state.login.isLoginSSOIntent);
const registrationErrorCode = registrationError?.errorCode;
Expand Down Expand Up @@ -172,6 +173,7 @@ const RegistrationForm = () => {
};

const handleUserRegistration = () => {
const userCountryCode = getCountryCookieValue();
let payload = { ...formFields, honor_code: true, terms_of_service: true };

if (currentProvider) {
Expand All @@ -184,6 +186,13 @@ const RegistrationForm = () => {
}
}

// add country in payload if country cookie value or mfe_context country exists
if (userCountryCode) {
payload.country = userCountryCode;
} else if (authContextCountryCode) {
payload.country = authContextCountryCode;
}

// Validating form data before submitting
const { isValid, fieldErrors } = isFormValid(
payload,
Expand Down
34 changes: 34 additions & 0 deletions src/forms/registration-popup/tests/RegistrationPopup.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import Cookies from 'universal-cookie';

import { setCurrentOpenedForm } from '../../../authn-component/data/reducers';
import {
Expand Down Expand Up @@ -75,6 +76,7 @@ describe('RegistrationForm Test', () => {
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE,
PRIVACY_POLICY: process.env.PRIVACY_POLICY,
USER_RETENTION_COOKIE_NAME: 'authn-returning-user',
ONBOARDING_COMPONENT_ENV: process.env.ONBOARDING_COMPONENT_ENV,
});
});

Expand Down Expand Up @@ -108,6 +110,38 @@ describe('RegistrationForm Test', () => {
expect(store.dispatch).toHaveBeenCalledWith(registerUser(payload));
});

it('should submit form with country code', async () => {
// Mock Cookies class
jest.mock('universal-cookie');

const cookie = new Cookies();
cookie.set(`${getConfig().ONBOARDING_COMPONENT_ENV}-edx-cf-loc`, 'US');

store.dispatch = jest.fn(store.dispatch);
const payload = {
email: '[email protected]',
name: 'test',
password: 'test-password12',
marketing_email_opt_in: true,
honor_code: true,
terms_of_service: true,
country: 'US',
};
const { container } = render(reduxWrapper(<IntlRegistrationForm />));

const emailInput = container.querySelector('#email');
fireEvent.change(emailInput, { target: { value: payload.email, name: 'email' } });

const nameInput = container.querySelector('#name');
const passwordInput = container.querySelector('#password');
const registerButton = container.querySelector('#register-user');
fireEvent.change(nameInput, { target: { value: payload.name, name: 'name' } });
fireEvent.change(passwordInput, { target: { value: payload.password, name: 'password' } });
fireEvent.click(registerButton);

expect(store.dispatch).toHaveBeenCalledWith(registerUser(payload));
});

it('should display an error when form is submitted with an invalid email', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const emailError = 'We couldn’t create your account. Please correct the errors below.';
Expand Down

0 comments on commit 58032c1

Please sign in to comment.