Compare commits
25 Commits
remove-com
...
manwar/VAN
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74fc221674 | ||
|
|
6b983e18d3 | ||
|
|
327210192c | ||
|
|
0d603b5fa1 | ||
|
|
efaa83a1bc | ||
|
|
bd63bb1f15 | ||
|
|
5754c2961a | ||
|
|
dcbd644a25 | ||
|
|
52e438652c | ||
|
|
d8947a4c0a | ||
|
|
03d1666c2c | ||
|
|
3782503983 | ||
|
|
b219fe3683 | ||
|
|
90f650ce3e | ||
|
|
6f325c20c3 | ||
|
|
de12dfbf9e | ||
|
|
c663f6fa30 | ||
|
|
dba93333fd | ||
|
|
611af07326 | ||
|
|
564ec70d9e | ||
|
|
65e95a4d1b | ||
|
|
cf2b50005b | ||
|
|
faf4ff8488 | ||
|
|
7d64220852 | ||
|
|
a18df02d37 |
1
.env
1
.env
@@ -23,6 +23,7 @@ POST_REGISTRATION_REDIRECT_URL=''
|
|||||||
SEARCH_CATALOG_URL=''
|
SEARCH_CATALOG_URL=''
|
||||||
# ***** Features flags *****
|
# ***** Features flags *****
|
||||||
DISABLE_ENTERPRISE_LOGIN=''
|
DISABLE_ENTERPRISE_LOGIN=''
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME=''
|
||||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
||||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
||||||
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
|
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -6781,9 +6781,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/algoliasearch-helper": {
|
"node_modules/algoliasearch-helper": {
|
||||||
"version": "3.16.3",
|
"version": "3.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.17.0.tgz",
|
||||||
"integrity": "sha512-1OuJT6sONAa9PxcOmWo5WCAT3jQSpCR9/m5Azujja7nhUQwAUDvaaAYrcmUySsrvHh74usZHbE3jFfGnWtZj8w==",
|
"integrity": "sha512-R5422OiQjvjlK3VdpNQ/Qk7KsTIGeM5ACm8civGifOVWdRRV/3SgXuKmeNxe94Dz6fwj/IgpVmXbHutU4mHubg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/events": "^4.0.1"
|
"@algolia/events": "^4.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const InstitutionLogistration = props => {
|
|||||||
className="btn nav-item p-0 mb-1 institutions--provider-link"
|
className="btn nav-item p-0 mb-1 institutions--provider-link"
|
||||||
destination={lmsBaseUrl + provider.loginUrl}
|
destination={lmsBaseUrl + provider.loginUrl}
|
||||||
>
|
>
|
||||||
{provider.name}
|
{provider?.name}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Navigate } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
|
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
import { setCookie } from '../data/utils';
|
import setCookie from '../data/utils/cookies';
|
||||||
|
|
||||||
const RedirectLogistration = (props) => {
|
const RedirectLogistration = (props) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
@@ -8,15 +9,20 @@ import { Login } from '@openedx/paragon/icons';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
import { LOGIN_PAGE, REGISTER_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||||
|
import { setCookie } from '../data/utils';
|
||||||
|
|
||||||
const SocialAuthProviders = (props) => {
|
const SocialAuthProviders = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { referrer, socialAuthProviders } = props;
|
const { referrer, socialAuthProviders } = props;
|
||||||
|
const registrationFields = useSelector(state => state.register.registrationFormData);
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (referrer === REGISTER_PAGE) {
|
||||||
|
setCookie('marketingEmailsOptIn', registrationFields?.configurableFormFields?.marketingEmailsOptIn);
|
||||||
|
}
|
||||||
const url = e.currentTarget.dataset.providerUrl;
|
const url = e.currentTarget.dataset.providerUrl;
|
||||||
window.location.href = getConfig().LMS_BASE_URL + url;
|
window.location.href = getConfig().LMS_BASE_URL + url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const ThirdPartyAuth = (props) => {
|
|||||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
const isSocialAuthActive = !!providers.length && !currentProvider;
|
||||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||||
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
||||||
|
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -61,7 +62,7 @@ const ThirdPartyAuth = (props) => {
|
|||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
{thirdPartyAuthApiStatus === PENDING_STATE && isThirdPartyAuthActive ? (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
79
src/common-components/data/constants.js
Normal file
79
src/common-components/data/constants.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
export const registerFields = {
|
||||||
|
fields: {
|
||||||
|
country: {
|
||||||
|
name: 'country',
|
||||||
|
error_message: 'Select your country or region of residence',
|
||||||
|
},
|
||||||
|
honor_code: {
|
||||||
|
name: 'honor_code',
|
||||||
|
type: 'tos_and_honor_code',
|
||||||
|
error_message: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const progressiveProfilingFields = {
|
||||||
|
extended_profile: [],
|
||||||
|
fields: {
|
||||||
|
level_of_education: {
|
||||||
|
name: 'level_of_education',
|
||||||
|
type: 'select',
|
||||||
|
label: 'Highest level of education completed',
|
||||||
|
error_message: '',
|
||||||
|
options: [
|
||||||
|
[
|
||||||
|
'p',
|
||||||
|
'Doctorate',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'm',
|
||||||
|
"Master's or professional degree",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'b',
|
||||||
|
"Bachelor's degree",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'a',
|
||||||
|
'Associate degree',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'hs',
|
||||||
|
'Secondary/high school',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'jhs',
|
||||||
|
'Junior secondary/junior high/middle school',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'none',
|
||||||
|
'No formal education',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'other',
|
||||||
|
'Other education',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
name: 'gender',
|
||||||
|
type: 'select',
|
||||||
|
label: 'Gender',
|
||||||
|
error_message: '',
|
||||||
|
options: [
|
||||||
|
[
|
||||||
|
'm',
|
||||||
|
'Male',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'f',
|
||||||
|
'Female',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'o',
|
||||||
|
'Other/Prefer Not to Say',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
getThirdPartyAuthContextSuccess,
|
getThirdPartyAuthContextSuccess,
|
||||||
THIRD_PARTY_AUTH_CONTEXT,
|
THIRD_PARTY_AUTH_CONTEXT,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
import { progressiveProfilingFields, registerFields } from './constants';
|
||||||
import {
|
import {
|
||||||
getThirdPartyAuthContext,
|
getThirdPartyAuthContext,
|
||||||
} from './service';
|
} from './service';
|
||||||
@@ -20,7 +22,16 @@ export function* fetchThirdPartyAuthContext(action) {
|
|||||||
} = yield call(getThirdPartyAuthContext, action.payload.urlParams);
|
} = yield call(getThirdPartyAuthContext, action.payload.urlParams);
|
||||||
|
|
||||||
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
|
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
|
||||||
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext));
|
// hard code country field, level of education and gender fields
|
||||||
|
if (getConfig().ENABLE_HARD_CODE_OPTIONAL_FIELDS) {
|
||||||
|
yield put(getThirdPartyAuthContextSuccess(
|
||||||
|
registerFields,
|
||||||
|
progressiveProfilingFields,
|
||||||
|
thirdPartyAuthContext,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(getThirdPartyAuthContextFailure());
|
yield put(getThirdPartyAuthContextFailure());
|
||||||
logError(e);
|
logError(e);
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import registerIcons from '../RegisterFaIcons';
|
import registerIcons from '../RegisterFaIcons';
|
||||||
import SocialAuthProviders from '../SocialAuthProviders';
|
import SocialAuthProviders from '../SocialAuthProviders';
|
||||||
|
|
||||||
registerIcons();
|
registerIcons();
|
||||||
|
const mockStore = configureStore();
|
||||||
|
|
||||||
describe('SocialAuthProviders', () => {
|
describe('SocialAuthProviders', () => {
|
||||||
let props = {};
|
let props = {};
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
register: {
|
||||||
|
registrationFormData: {
|
||||||
|
configurableFormFields: {
|
||||||
|
marketingEmailsOptIn: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = mockStore(initialState);
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
const appleProvider = {
|
const appleProvider = {
|
||||||
id: 'oa2-apple-id',
|
id: 'oa2-apple-id',
|
||||||
name: 'Apple',
|
name: 'Apple',
|
||||||
@@ -30,11 +49,11 @@ describe('SocialAuthProviders', () => {
|
|||||||
it('should match social auth provider with iconImage snapshot', () => {
|
it('should match social auth provider with iconImage snapshot', () => {
|
||||||
props = { socialAuthProviders: [appleProvider, facebookProvider] };
|
props = { socialAuthProviders: [appleProvider, facebookProvider] };
|
||||||
|
|
||||||
const tree = renderer.create(
|
const tree = renderer.create(reduxWrapper(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<SocialAuthProviders {...props} />
|
<SocialAuthProviders {...props} />
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
).toJSON();
|
)).toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -48,11 +67,11 @@ describe('SocialAuthProviders', () => {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
const tree = renderer.create(
|
const tree = renderer.create(reduxWrapper(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<SocialAuthProviders {...props} />
|
<SocialAuthProviders {...props} />
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
).toJSON();
|
)).toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -66,11 +85,11 @@ describe('SocialAuthProviders', () => {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
const tree = renderer.create(
|
const tree = renderer.create(reduxWrapper(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<SocialAuthProviders {...props} />
|
<SocialAuthProviders {...props} />
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
).toJSON();
|
)).toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ const configuration = {
|
|||||||
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
|
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
|
||||||
// Features
|
// Features
|
||||||
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME: process.env.ENABLE_AUTO_GENERATED_USERNAME || false,
|
||||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
|
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
|
||||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
|
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
|
||||||
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,
|
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,
|
||||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
||||||
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
||||||
SHOW_REGISTRATION_LINKS: process.env.SHOW_REGISTRATION_LINKS !== 'false',
|
SHOW_REGISTRATION_LINKS: process.env.SHOW_REGISTRATION_LINKS !== 'false',
|
||||||
|
ENABLE_HARD_CODE_OPTIONAL_FIELDS: process.env.ENABLE_HARD_CODE_OPTIONAL_FIELDS || false,
|
||||||
ENABLE_IMAGE_LAYOUT: process.env.ENABLE_IMAGE_LAYOUT || false,
|
ENABLE_IMAGE_LAYOUT: process.env.ENABLE_IMAGE_LAYOUT || false,
|
||||||
// Links
|
// Links
|
||||||
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
||||||
@@ -34,6 +36,7 @@ const configuration = {
|
|||||||
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
|
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
|
||||||
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
|
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
|
||||||
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
|
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
|
||||||
|
AUTO_GENERATED_USERNAME_EXPERIMENT_ID: process.env.AUTO_GENERATED_USERNAME_EXPERIMENT_ID || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default configuration;
|
export default configuration;
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
|
|||||||
|
|
||||||
// Query string parameters that can be passed to LMS to manage
|
// Query string parameters that can be passed to LMS to manage
|
||||||
// things like auto-enrollment upon login and registration.
|
// things like auto-enrollment upon login and registration.
|
||||||
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta'];
|
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'country', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta', 'levelOfEducation', 'finishAuthUrl'];
|
||||||
export const REDIRECT = 'redirect';
|
export const REDIRECT = 'redirect';
|
||||||
|
export const APP_NAME = 'authn_mfe';
|
||||||
|
|||||||
37
src/data/segment/utils.js
Normal file
37
src/data/segment/utils.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
|
|
||||||
|
import { APP_NAME } from '../constants';
|
||||||
|
|
||||||
|
export const LINK_TIMEOUT = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event tracker function that sends a tracking event with the given name and options.
|
||||||
|
*
|
||||||
|
* @param {string} name - The name of the event to be tracked.
|
||||||
|
* @param {object} [options={}] - Additional options to be included with the event.
|
||||||
|
* @returns {function} - A function that, when called, sends the tracking event.
|
||||||
|
*/
|
||||||
|
export const createEventTracker = (name, options = {}) => () => sendTrackEvent(
|
||||||
|
name,
|
||||||
|
{ ...options, app_name: APP_NAME },
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event tracker function that sends a tracking event with the given name and options.
|
||||||
|
*
|
||||||
|
* @param {string} name - The name of the event to be tracked.
|
||||||
|
* @param {object} [options={}] - Additional options to be included with the event.
|
||||||
|
* @returns {function} - A function that, when called, sends the tracking event.
|
||||||
|
*/
|
||||||
|
export const createPageEventTracker = (name, options = null) => () => sendPageEvent(
|
||||||
|
name,
|
||||||
|
options,
|
||||||
|
{ app_name: APP_NAME },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createLinkTracker = (tracker, href) => (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
tracker();
|
||||||
|
return setTimeout(() => { window.location.href = href; }, LINK_TIMEOUT);
|
||||||
|
};
|
||||||
@@ -11,3 +11,11 @@ export default function setCookie(cookieName, cookieValue, cookieExpiry) {
|
|||||||
cookies.set(cookieName, cookieValue, options);
|
cookies.set(cookieName, cookieValue, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeCookie(cookieName) {
|
||||||
|
if (cookieName) {
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
||||||
|
cookies.remove(cookieName, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ export {
|
|||||||
windowScrollTo,
|
windowScrollTo,
|
||||||
} from './dataUtils';
|
} from './dataUtils';
|
||||||
export { default as AsyncActionType } from './reduxUtils';
|
export { default as AsyncActionType } from './reduxUtils';
|
||||||
export { default as setCookie } from './cookies';
|
export { default as setCookie, removeCookie } from './cookies';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -25,6 +24,10 @@ import BaseContainer from '../base-container';
|
|||||||
import { FormGroup } from '../common-components';
|
import { FormGroup } from '../common-components';
|
||||||
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||||
|
import {
|
||||||
|
trackForgotPasswordPageEvent,
|
||||||
|
trackForgotPasswordPageViewed,
|
||||||
|
} from '../tracking/trackers/forgotpassword';
|
||||||
|
|
||||||
const ForgotPasswordPage = (props) => {
|
const ForgotPasswordPage = (props) => {
|
||||||
const platformName = getConfig().SITE_NAME;
|
const platformName = getConfig().SITE_NAME;
|
||||||
@@ -41,8 +44,8 @@ const ForgotPasswordPage = (props) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sendPageEvent('login_and_registration', 'reset');
|
trackForgotPasswordPageEvent();
|
||||||
sendTrackEvent('edx.bi.password_reset_form.viewed', { category: 'user-engagement' });
|
trackForgotPasswordPageViewed();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
|
||||||
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form, StatefulButton,
|
Form, StatefulButton,
|
||||||
@@ -43,6 +42,9 @@ import {
|
|||||||
updatePathWithQueryParams,
|
updatePathWithQueryParams,
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
||||||
|
import {
|
||||||
|
trackForgotPasswordLinkClick, trackLoginPageViewed, trackLoginSuccess,
|
||||||
|
} from '../tracking/trackers/login';
|
||||||
|
|
||||||
const LoginPage = (props) => {
|
const LoginPage = (props) => {
|
||||||
const {
|
const {
|
||||||
@@ -78,9 +80,15 @@ const LoginPage = (props) => {
|
|||||||
const tpaHint = getTpaHint();
|
const tpaHint = getTpaHint();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sendPageEvent('login_and_registration', 'login');
|
trackLoginPageViewed();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loginResult.success) {
|
||||||
|
trackLoginSuccess();
|
||||||
|
}
|
||||||
|
}, [loginResult]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const payload = { ...queryParams };
|
const payload = { ...queryParams };
|
||||||
if (tpaHint) {
|
if (tpaHint) {
|
||||||
@@ -170,9 +178,6 @@ const LoginPage = (props) => {
|
|||||||
const { name } = event.target;
|
const { name } = event.target;
|
||||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||||
};
|
};
|
||||||
const trackForgotPasswordLinkClick = () => {
|
|
||||||
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import { act } from 'react-dom/test-utils';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import { COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE } from '../../data/constants';
|
import {
|
||||||
|
APP_NAME, COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE,
|
||||||
|
} from '../../data/constants';
|
||||||
import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from '../data/actions';
|
import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from '../data/actions';
|
||||||
import { INTERNAL_SERVER_ERROR } from '../data/constants';
|
import { INTERNAL_SERVER_ERROR } from '../data/constants';
|
||||||
import LoginPage from '../LoginPage';
|
import LoginPage from '../LoginPage';
|
||||||
@@ -751,7 +753,7 @@ describe('LoginPage', () => {
|
|||||||
|
|
||||||
it('should send page event when login page is rendered', () => {
|
it('should send page event when login page is rendered', () => {
|
||||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
|
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login', { app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tests that form is in invalid state when it is submitted', () => {
|
it('tests that form is in invalid state when it is submitted', () => {
|
||||||
@@ -784,7 +786,7 @@ describe('LoginPage', () => {
|
|||||||
{ selector: '#forgot-password' },
|
{ selector: '#forgot-password' },
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.password-reset_form.toggled', { category: 'user-engagement', app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should backup the login form state when shouldBackupState is true', () => {
|
it('should backup the login form state when shouldBackupState is true', () => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
tpaProvidersSelector,
|
tpaProvidersSelector,
|
||||||
} from '../common-components/data/selectors';
|
} from '../common-components/data/selectors';
|
||||||
import messages from '../common-components/messages';
|
import messages from '../common-components/messages';
|
||||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
import { APP_NAME, LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||||
import {
|
import {
|
||||||
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
@@ -56,11 +56,11 @@ const Logistration = (props) => {
|
|||||||
}, [navigate, disablePublicAccountCreation]);
|
}, [navigate, disablePublicAccountCreation]);
|
||||||
|
|
||||||
const handleInstitutionLogin = (e) => {
|
const handleInstitutionLogin = (e) => {
|
||||||
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement', app_name: APP_NAME });
|
||||||
if (typeof e === 'string') {
|
if (typeof e === 'string') {
|
||||||
sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register');
|
sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register', { app_name: APP_NAME });
|
||||||
} else {
|
} else {
|
||||||
sendPageEvent('login_and_registration', e.target.dataset.eventName);
|
sendPageEvent('login_and_registration', e.target.dataset.eventName, { app_name: APP_NAME });
|
||||||
}
|
}
|
||||||
|
|
||||||
setInstitutionLogin(!institutionLogin);
|
setInstitutionLogin(!institutionLogin);
|
||||||
@@ -70,7 +70,7 @@ const Logistration = (props) => {
|
|||||||
if (tabKey === currentTab) {
|
if (tabKey === currentTab) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement', app_name: APP_NAME });
|
||||||
props.clearThirdPartyAuthContextErrorMessage();
|
props.clearThirdPartyAuthContextErrorMessage();
|
||||||
if (tabKey === LOGIN_PAGE) {
|
if (tabKey === LOGIN_PAGE) {
|
||||||
props.backupRegistrationForm();
|
props.backupRegistrationForm();
|
||||||
|
|||||||
@@ -11,16 +11,21 @@ import configureStore from 'redux-mock-store';
|
|||||||
import Logistration from './Logistration';
|
import Logistration from './Logistration';
|
||||||
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
|
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
|
||||||
import {
|
import {
|
||||||
|
APP_NAME,
|
||||||
COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE,
|
COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
import { backupLoginForm } from '../login/data/actions';
|
import { backupLoginForm } from '../login/data/actions';
|
||||||
import { backupRegistrationForm } from '../register/data/actions';
|
import { backupRegistrationForm } from '../register/data/actions';
|
||||||
|
import { NOT_INITIALIZED } from '../register/data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation
|
||||||
|
from '../register/data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
sendPageEvent: jest.fn(),
|
sendPageEvent: jest.fn(),
|
||||||
sendTrackEvent: jest.fn(),
|
sendTrackEvent: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('../register/data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
|
||||||
|
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
const IntlLogistration = injectIntl(Logistration);
|
const IntlLogistration = injectIntl(Logistration);
|
||||||
@@ -84,6 +89,7 @@ describe('Logistration', () => {
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
|
||||||
configure({
|
configure({
|
||||||
loggingService: { logError: jest.fn() },
|
loggingService: { logError: jest.fn() },
|
||||||
config: {
|
config: {
|
||||||
@@ -224,8 +230,8 @@ describe('Logistration', () => {
|
|||||||
render(reduxWrapper(<IntlLogistration {...props} />));
|
render(reduxWrapper(<IntlLogistration {...props} />));
|
||||||
fireEvent.click(screen.getByText('Institution/campus credentials'));
|
fireEvent.click(screen.getByText('Institution/campus credentials'));
|
||||||
|
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement', app_name: APP_NAME });
|
||||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
|
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login', { app_name: APP_NAME });
|
||||||
|
|
||||||
mergeConfig({
|
mergeConfig({
|
||||||
DISABLE_ENTERPRISE_LOGIN: '',
|
DISABLE_ENTERPRISE_LOGIN: '',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||||
import { identifyAuthenticatedUser, sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
import { identifyAuthenticatedUser } from '@edx/frontend-platform/analytics';
|
||||||
import {
|
import {
|
||||||
AxiosJwtAuthService,
|
AxiosJwtAuthService,
|
||||||
configure as configureAuth,
|
configure as configureAuth,
|
||||||
@@ -39,6 +39,13 @@ import {
|
|||||||
import isOneTrustFunctionalCookieEnabled from '../data/oneTrust';
|
import isOneTrustFunctionalCookieEnabled from '../data/oneTrust';
|
||||||
import { getAllPossibleQueryParams, isHostAvailableInQueryParams } from '../data/utils';
|
import { getAllPossibleQueryParams, isHostAvailableInQueryParams } from '../data/utils';
|
||||||
import { FormFieldRenderer } from '../field-renderer';
|
import { FormFieldRenderer } from '../field-renderer';
|
||||||
|
import {
|
||||||
|
trackDisablePostRegistrationRecommendations,
|
||||||
|
trackProgressiveProfilingPageViewed,
|
||||||
|
trackProgressiveProfilingSkipLinkClick,
|
||||||
|
trackProgressiveProfilingSubmitClick,
|
||||||
|
trackProgressiveProfilingSupportLinkCLick,
|
||||||
|
} from '../tracking/trackers/progressive-profiling';
|
||||||
|
|
||||||
const ProgressiveProfiling = (props) => {
|
const ProgressiveProfiling = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -98,14 +105,13 @@ const ProgressiveProfiling = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (authenticatedUser?.userId) {
|
if (authenticatedUser?.userId) {
|
||||||
identifyAuthenticatedUser(authenticatedUser.userId);
|
identifyAuthenticatedUser(authenticatedUser.userId);
|
||||||
sendPageEvent('login_and_registration', 'welcome');
|
trackProgressiveProfilingPageViewed();
|
||||||
}
|
}
|
||||||
}, [authenticatedUser]);
|
}, [authenticatedUser]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enablePostRegistrationRecommendations) {
|
if (!enablePostRegistrationRecommendations) {
|
||||||
sendTrackEvent(
|
trackDisablePostRegistrationRecommendations(
|
||||||
'edx.bi.user.recommendations.not.enabled',
|
|
||||||
{ functionalCookiesConsent, page: 'authn_recommendations' },
|
{ functionalCookiesConsent, page: 'authn_recommendations' },
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -149,29 +155,23 @@ const ProgressiveProfiling = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
|
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
|
||||||
|
const eventProperties = {
|
||||||
sendTrackEvent(
|
isGenderSelected: !!values.gender,
|
||||||
'edx.bi.welcome.page.submit.clicked',
|
isYearOfBirthSelected: !!values.year_of_birth,
|
||||||
{
|
isLevelOfEducationSelected: !!values.level_of_education,
|
||||||
isGenderSelected: !!values.gender,
|
isWorkExperienceSelected: !!values.work_experience,
|
||||||
isYearOfBirthSelected: !!values.year_of_birth,
|
host: queryParams?.host || '',
|
||||||
isLevelOfEducationSelected: !!values.level_of_education,
|
};
|
||||||
isWorkExperienceSelected: !!values.work_experience,
|
trackProgressiveProfilingSubmitClick(eventProperties);
|
||||||
host: queryParams?.host || '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSkip = (e) => {
|
const handleSkip = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.history.replaceState(location.state, null, '');
|
window.history.replaceState(location.state, null, '');
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
sendTrackEvent(
|
trackProgressiveProfilingSkipLinkClick({
|
||||||
'edx.bi.welcome.page.skip.link.clicked',
|
host: queryParams?.host || '',
|
||||||
{
|
});
|
||||||
host: queryParams?.host || '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeHandler = (e) => {
|
const onChangeHandler = (e) => {
|
||||||
@@ -242,7 +242,7 @@ const ProgressiveProfiling = (props) => {
|
|||||||
destination={getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK}
|
destination={getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
showLaunchIcon={false}
|
showLaunchIcon={false}
|
||||||
onClick={() => (sendTrackEvent('edx.bi.welcome.page.support.link.clicked'))}
|
onClick={() => (trackProgressiveProfilingSupportLinkCLick())}
|
||||||
>
|
>
|
||||||
{formatMessage(messages['optional.fields.information.link'])}
|
{formatMessage(messages['optional.fields.information.link'])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { MemoryRouter, mockNavigate, useLocation } from 'react-router-dom';
|
|||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
APP_NAME,
|
||||||
AUTHN_PROGRESSIVE_PROFILING,
|
AUTHN_PROGRESSIVE_PROFILING,
|
||||||
COMPLETE_STATE, DEFAULT_REDIRECT_URL,
|
COMPLETE_STATE, DEFAULT_REDIRECT_URL,
|
||||||
EMBEDDED,
|
EMBEDDED,
|
||||||
@@ -143,8 +144,9 @@ describe('ProgressiveProfilingTests', () => {
|
|||||||
const modalContentContainer = document.getElementsByClassName('.pgn__modal-content-container');
|
const modalContentContainer = document.getElementsByClassName('.pgn__modal-content-container');
|
||||||
|
|
||||||
expect(modalContentContainer).toBeTruthy();
|
expect(modalContentContainer).toBeTruthy();
|
||||||
|
const payload = { host: '', app_name: APP_NAME };
|
||||||
|
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host: '' });
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ******** test event functionality ********
|
// ******** test event functionality ********
|
||||||
@@ -165,7 +167,7 @@ describe('ProgressiveProfilingTests', () => {
|
|||||||
const supportLink = screen.getByRole('link', { name: /learn more about how we use this information/i });
|
const supportLink = screen.getByRole('link', { name: /learn more about how we use this information/i });
|
||||||
fireEvent.click(supportLink);
|
fireEvent.click(supportLink);
|
||||||
|
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked', { app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set empty host property value for non-embedded experience', () => {
|
it('should set empty host property value for non-embedded experience', () => {
|
||||||
@@ -175,6 +177,7 @@ describe('ProgressiveProfilingTests', () => {
|
|||||||
isLevelOfEducationSelected: false,
|
isLevelOfEducationSelected: false,
|
||||||
isWorkExperienceSelected: false,
|
isWorkExperienceSelected: false,
|
||||||
host: '',
|
host: '',
|
||||||
|
app_name: APP_NAME,
|
||||||
};
|
};
|
||||||
delete window.location;
|
delete window.location;
|
||||||
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
|
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
|
||||||
@@ -316,7 +319,7 @@ describe('ProgressiveProfilingTests', () => {
|
|||||||
const skipLinkButton = screen.getByText('Skip for now');
|
const skipLinkButton = screen.getByText('Skip for now');
|
||||||
fireEvent.click(skipLinkButton);
|
fireEvent.click(skipLinkButton);
|
||||||
|
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host });
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host, app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show spinner while fetching the optional fields', () => {
|
it('should show spinner while fetching the optional fields', () => {
|
||||||
@@ -349,6 +352,7 @@ describe('ProgressiveProfilingTests', () => {
|
|||||||
isLevelOfEducationSelected: false,
|
isLevelOfEducationSelected: false,
|
||||||
isWorkExperienceSelected: false,
|
isWorkExperienceSelected: false,
|
||||||
host: 'http://example.com',
|
host: 'http://example.com',
|
||||||
|
app_name: APP_NAME,
|
||||||
};
|
};
|
||||||
delete window.location;
|
delete window.location;
|
||||||
window.location = {
|
window.location = {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
breakpoints,
|
breakpoints,
|
||||||
@@ -21,18 +22,35 @@ import RecommendationsLargeLayout from './RecommendationsPageLayouts/LargeLayout
|
|||||||
import RecommendationsSmallLayout from './RecommendationsPageLayouts/SmallLayout';
|
import RecommendationsSmallLayout from './RecommendationsPageLayouts/SmallLayout';
|
||||||
import { LINK_TIMEOUT, trackRecommendationsViewed, trackSkipButtonClicked } from './track';
|
import { LINK_TIMEOUT, trackRecommendationsViewed, trackSkipButtonClicked } from './track';
|
||||||
import { DEFAULT_REDIRECT_URL } from '../data/constants';
|
import { DEFAULT_REDIRECT_URL } from '../data/constants';
|
||||||
|
import { getAllPossibleQueryParams } from '../data/utils';
|
||||||
|
|
||||||
const RecommendationsPage = () => {
|
const RecommendationsPage = () => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||||
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth - 1 });
|
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth - 1 });
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const queryParams = getAllPossibleQueryParams();
|
||||||
|
// flag to show recommendations for onboarding component experience
|
||||||
|
const showRecommendations = !!queryParams?.finalRedirectUrl && !!queryParams?.country;
|
||||||
|
const backendCountryCode = useSelector((state) => state.register.backendCountryCode);
|
||||||
|
|
||||||
const registrationResponse = location.state?.registrationResult;
|
const [redirectUrl, setRedirectUrl] = useState(location.state?.registrationResult?.redirectUrl);
|
||||||
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
const [educationLevel, setEducationLevel] = useState(EDUCATION_LEVEL_MAPPING[location.state?.educationLevel]);
|
||||||
const educationLevel = EDUCATION_LEVEL_MAPPING[location.state?.educationLevel];
|
const [userId, setUserId] = useState(location.state?.userId || null);
|
||||||
const userId = location.state?.userId;
|
const [userCountry, setUserCountry] = useState(backendCountryCode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showRecommendations) {
|
||||||
|
const authenticatedUser = getAuthenticatedUser();
|
||||||
|
if (authenticatedUser) {
|
||||||
|
setRedirectUrl(queryParams.finalRedirectUrl);
|
||||||
|
setEducationLevel(EDUCATION_LEVEL_MAPPING[queryParams?.levelOfEducation]);
|
||||||
|
setUserCountry(queryParams.country);
|
||||||
|
setUserId(authenticatedUser?.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [showRecommendations, queryParams]);
|
||||||
|
|
||||||
const userCountry = useSelector((state) => state.register.backendCountryCode);
|
|
||||||
const {
|
const {
|
||||||
recommendations: algoliaRecommendations,
|
recommendations: algoliaRecommendations,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -46,8 +64,8 @@ const RecommendationsPage = () => {
|
|||||||
|
|
||||||
const handleSkipRecommendationPage = () => {
|
const handleSkipRecommendationPage = () => {
|
||||||
window.history.replaceState(location.state, null, '');
|
window.history.replaceState(location.state, null, '');
|
||||||
if (registrationResponse) {
|
if (redirectUrl) {
|
||||||
window.location.href = registrationResponse.redirectUrl;
|
window.location.href = redirectUrl;
|
||||||
} else {
|
} else {
|
||||||
window.location.href = DASHBOARD_URL;
|
window.location.href = DASHBOARD_URL;
|
||||||
}
|
}
|
||||||
@@ -59,7 +77,7 @@ const RecommendationsPage = () => {
|
|||||||
setTimeout(() => { handleSkipRecommendationPage(); }, LINK_TIMEOUT);
|
setTimeout(() => { handleSkipRecommendationPage(); }, LINK_TIMEOUT);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!registrationResponse) {
|
if (!redirectUrl && !showRecommendations) {
|
||||||
window.location.href = DASHBOARD_URL;
|
window.location.href = DASHBOARD_URL;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const CountryField = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getCountryList = () => countryList.map((country) => (
|
const getCountryList = () => countryList.map((country) => (
|
||||||
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]} id={country[COUNTRY_CODE_KEY]}>
|
<FormAutosuggestOption key={country[COUNTRY_DISPLAY_KEY]} id={country[COUNTRY_CODE_KEY]}>
|
||||||
{country[COUNTRY_DISPLAY_KEY]}
|
{country[COUNTRY_DISPLAY_KEY]}
|
||||||
</FormAutosuggestOption>
|
</FormAutosuggestOption>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import React, {
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Form, Spinner, StatefulButton } from '@openedx/paragon';
|
import { Form, Spinner, StatefulButton } from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -18,6 +17,7 @@ import {
|
|||||||
backupRegistrationFormBegin,
|
backupRegistrationFormBegin,
|
||||||
clearRegistrationBackendError,
|
clearRegistrationBackendError,
|
||||||
registerNewUser,
|
registerNewUser,
|
||||||
|
setAutoGeneratedUsernameExperimentData,
|
||||||
setEmailSuggestionInStore,
|
setEmailSuggestionInStore,
|
||||||
setUserPipelineDataLoaded,
|
setUserPipelineDataLoaded,
|
||||||
} from './data/actions';
|
} from './data/actions';
|
||||||
@@ -25,6 +25,8 @@ import {
|
|||||||
FORM_SUBMISSION_ERROR,
|
FORM_SUBMISSION_ERROR,
|
||||||
TPA_AUTHENTICATION_FAILURE,
|
TPA_AUTHENTICATION_FAILURE,
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
|
import { AUTO_GENERATED_USERNAME_REGISTRATION_EXP_VARIATION, NOT_INITIALIZED } from './data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
import getBackendValidations from './data/selectors';
|
import getBackendValidations from './data/selectors';
|
||||||
import {
|
import {
|
||||||
isFormValid, prepareRegistrationPayload,
|
isFormValid, prepareRegistrationPayload,
|
||||||
@@ -41,11 +43,12 @@ import { getThirdPartyAuthContext as getRegistrationDataFromBackend } from '../c
|
|||||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||||
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
||||||
import {
|
import {
|
||||||
COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
APP_NAME, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
import {
|
import {
|
||||||
getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, setCookie,
|
getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, removeCookie, setCookie,
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
|
import { trackRegistrationPageViewed, trackRegistrationSuccess } from '../tracking/trackers/register';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Registration Page component
|
* Main Registration Page component
|
||||||
@@ -60,6 +63,7 @@ const RegistrationPage = (props) => {
|
|||||||
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
|
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
|
||||||
showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS,
|
showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS,
|
||||||
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
||||||
|
autoGeneratedUsernameEnabled: getConfig().ENABLE_AUTO_GENERATED_USERNAME,
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
handleInstitutionLogin,
|
handleInstitutionLogin,
|
||||||
@@ -67,6 +71,7 @@ const RegistrationPage = (props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const backedUpFormData = useSelector(state => state.register.registrationFormData);
|
const backedUpFormData = useSelector(state => state.register.registrationFormData);
|
||||||
|
const initExpVariation = useSelector(state => state.register.autoGeneratedUsernameExperimentVariation);
|
||||||
const registrationError = useSelector(state => state.register.registrationError);
|
const registrationError = useSelector(state => state.register.registrationError);
|
||||||
const registrationErrorCode = registrationError?.errorCode;
|
const registrationErrorCode = registrationError?.errorCode;
|
||||||
const registrationResult = useSelector(state => state.register.registrationResult);
|
const registrationResult = useSelector(state => state.register.registrationResult);
|
||||||
@@ -102,6 +107,12 @@ const RegistrationPage = (props) => {
|
|||||||
? formatMessage(messages['create.account.cta.button'], { label: cta })
|
? formatMessage(messages['create.account.cta.button'], { label: cta })
|
||||||
: formatMessage(messages['create.account.for.free.button']);
|
: formatMessage(messages['create.account.for.free.button']);
|
||||||
|
|
||||||
|
const autoGeneratedUsernameExpVariation = useAutoGeneratedUsernameExperimentVariation(
|
||||||
|
initExpVariation, registrationEmbedded, tpaHint, currentProvider, thirdPartyAuthApiStatus,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hideUsernameField = flags.autoGeneratedUsernameEnabled
|
||||||
|
|| autoGeneratedUsernameExpVariation === AUTO_GENERATED_USERNAME_REGISTRATION_EXP_VARIATION;
|
||||||
/**
|
/**
|
||||||
* Set the userPipelineDetails data in formFields for only first time
|
* Set the userPipelineDetails data in formFields for only first time
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +138,7 @@ const RegistrationPage = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!formStartTime) {
|
if (!formStartTime) {
|
||||||
sendPageEvent('login_and_registration', 'register');
|
trackRegistrationPageViewed();
|
||||||
const payload = { ...queryParams, is_register_page: true };
|
const payload = { ...queryParams, is_register_page: true };
|
||||||
if (tpaHint) {
|
if (tpaHint) {
|
||||||
payload.tpa_hint = tpaHint;
|
payload.tpa_hint = tpaHint;
|
||||||
@@ -148,8 +159,10 @@ const RegistrationPage = (props) => {
|
|||||||
formFields: { ...formFields },
|
formFields: { ...formFields },
|
||||||
errors: { ...errors },
|
errors: { ...errors },
|
||||||
}));
|
}));
|
||||||
|
dispatch(setAutoGeneratedUsernameExperimentData(autoGeneratedUsernameExpVariation));
|
||||||
}
|
}
|
||||||
}, [shouldBackupState, configurableFormFields, formFields, errors, dispatch, backedUpFormData]);
|
}, [shouldBackupState, configurableFormFields, // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
formFields, errors, dispatch, backedUpFormData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (backendValidations) {
|
if (backendValidations) {
|
||||||
@@ -170,10 +183,13 @@ const RegistrationPage = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (registrationResult.success) {
|
if (registrationResult.success) {
|
||||||
// This event is used by GTM
|
// This event is used by GTM
|
||||||
sendTrackEvent('edx.bi.user.account.registered.client', {});
|
trackRegistrationSuccess();
|
||||||
|
|
||||||
// This is used by the "User Retention Rate Event" on GTM
|
// This is used by the "User Retention Rate Event" on GTM
|
||||||
setCookie(getConfig().USER_RETENTION_COOKIE_NAME, true);
|
setCookie(getConfig().USER_RETENTION_COOKIE_NAME, true);
|
||||||
|
|
||||||
|
// remove marketingEmailsOptIn cookie that was set on SSO registration flow
|
||||||
|
removeCookie('marketingEmailsOptIn');
|
||||||
}
|
}
|
||||||
}, [registrationResult]);
|
}, [registrationResult]);
|
||||||
|
|
||||||
@@ -209,12 +225,15 @@ const RegistrationPage = (props) => {
|
|||||||
|
|
||||||
const registerUser = () => {
|
const registerUser = () => {
|
||||||
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
|
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
|
||||||
let payload = { ...formFields };
|
let payload = { ...formFields, app_name: APP_NAME };
|
||||||
|
|
||||||
if (currentProvider) {
|
if (currentProvider) {
|
||||||
delete payload.password;
|
delete payload.password;
|
||||||
payload.social_auth_provider = currentProvider;
|
payload.social_auth_provider = currentProvider;
|
||||||
}
|
}
|
||||||
|
if (hideUsernameField) {
|
||||||
|
delete payload.username;
|
||||||
|
}
|
||||||
|
|
||||||
// Validating form data before submitting
|
// Validating form data before submitting
|
||||||
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
|
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
|
||||||
@@ -282,104 +301,109 @@ const RegistrationPage = (props) => {
|
|||||||
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && !!Object.keys(optionalFields.fields).length
|
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && !!Object.keys(optionalFields.fields).length
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{autoSubmitRegForm && !errorCode.type ? (
|
{(autoSubmitRegForm && !errorCode.type)
|
||||||
<div className="mw-xs mt-5 text-center">
|
|| (!autoGeneratedUsernameExpVariation && !(
|
||||||
<Spinner animation="border" variant="primary" id="tpa-spinner" />
|
autoGeneratedUsernameExpVariation === NOT_INITIALIZED
|
||||||
</div>
|
|| registrationEmbedded || !!tpaHint || !!currentProvider))
|
||||||
) : (
|
? (
|
||||||
<div
|
<div className="mw-xs mt-5 text-center">
|
||||||
className={classNames(
|
<Spinner animation="border" variant="primary" id="tpa-spinner" />
|
||||||
'mw-xs mt-3',
|
</div>
|
||||||
{ 'w-100 m-auto pt-4 main-content': registrationEmbedded },
|
) : (
|
||||||
)}
|
<div
|
||||||
>
|
className={classNames(
|
||||||
<ThirdPartyAuthAlert
|
'mw-xs mt-3',
|
||||||
currentProvider={currentProvider}
|
{ 'w-100 m-auto pt-4 main-content': registrationEmbedded },
|
||||||
platformName={platformName}
|
)}
|
||||||
referrer={REGISTER_PAGE}
|
>
|
||||||
/>
|
<ThirdPartyAuthAlert
|
||||||
<RegistrationFailure
|
currentProvider={currentProvider}
|
||||||
errorCode={errorCode.type}
|
platformName={platformName}
|
||||||
failureCount={errorCode.count}
|
referrer={REGISTER_PAGE}
|
||||||
context={{ provider: currentProvider, errorMessage: thirdPartyAuthErrorMessage }}
|
|
||||||
/>
|
|
||||||
<Form id="registration-form" name="registration-form">
|
|
||||||
<NameField
|
|
||||||
name="name"
|
|
||||||
value={formFields.name}
|
|
||||||
shouldFetchUsernameSuggestions={!formFields.username.trim()}
|
|
||||||
handleChange={handleOnChange}
|
|
||||||
handleErrorChange={handleErrorChange}
|
|
||||||
errorMessage={errors.name}
|
|
||||||
helpText={[formatMessage(messages['help.text.name'])]}
|
|
||||||
floatingLabel={formatMessage(messages['registration.fullname.label'])}
|
|
||||||
/>
|
/>
|
||||||
<EmailField
|
<RegistrationFailure
|
||||||
name="email"
|
errorCode={errorCode.type}
|
||||||
value={formFields.email}
|
failureCount={errorCode.count}
|
||||||
confirmEmailValue={configurableFormFields?.confirm_email}
|
context={{ provider: currentProvider, errorMessage: thirdPartyAuthErrorMessage }}
|
||||||
handleErrorChange={handleErrorChange}
|
|
||||||
handleChange={handleOnChange}
|
|
||||||
errorMessage={errors.email}
|
|
||||||
helpText={[formatMessage(messages['help.text.email'])]}
|
|
||||||
floatingLabel={formatMessage(messages['registration.email.label'])}
|
|
||||||
/>
|
/>
|
||||||
<UsernameField
|
<Form id="registration-form" name="registration-form">
|
||||||
name="username"
|
<NameField
|
||||||
spellCheck="false"
|
name="name"
|
||||||
value={formFields.username}
|
value={formFields.name}
|
||||||
handleChange={handleOnChange}
|
shouldFetchUsernameSuggestions={!formFields.username.trim()}
|
||||||
handleErrorChange={handleErrorChange}
|
|
||||||
errorMessage={errors.username}
|
|
||||||
helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]}
|
|
||||||
floatingLabel={formatMessage(messages['registration.username.label'])}
|
|
||||||
/>
|
|
||||||
{!currentProvider && (
|
|
||||||
<PasswordField
|
|
||||||
name="password"
|
|
||||||
value={formFields.password}
|
|
||||||
handleChange={handleOnChange}
|
handleChange={handleOnChange}
|
||||||
handleErrorChange={handleErrorChange}
|
handleErrorChange={handleErrorChange}
|
||||||
errorMessage={errors.password}
|
errorMessage={errors.name}
|
||||||
floatingLabel={formatMessage(messages['registration.password.label'])}
|
helpText={[formatMessage(messages['help.text.name'])]}
|
||||||
|
floatingLabel={formatMessage(messages['registration.fullname.label'])}
|
||||||
/>
|
/>
|
||||||
)}
|
<EmailField
|
||||||
<ConfigurableRegistrationForm
|
name="email"
|
||||||
email={formFields.email}
|
value={formFields.email}
|
||||||
fieldErrors={errors}
|
confirmEmailValue={configurableFormFields?.confirm_email}
|
||||||
formFields={configurableFormFields}
|
handleErrorChange={handleErrorChange}
|
||||||
setFieldErrors={registrationEmbedded ? setTemporaryErrors : setErrors}
|
handleChange={handleOnChange}
|
||||||
setFormFields={setConfigurableFormFields}
|
errorMessage={errors.email}
|
||||||
autoSubmitRegisterForm={autoSubmitRegForm}
|
helpText={[formatMessage(messages['help.text.email'])]}
|
||||||
fieldDescriptions={fieldDescriptions}
|
floatingLabel={formatMessage(messages['registration.email.label'])}
|
||||||
/>
|
|
||||||
<StatefulButton
|
|
||||||
id="register-user"
|
|
||||||
name="register-user"
|
|
||||||
type="submit"
|
|
||||||
variant="brand"
|
|
||||||
className="register-button mt-4 mb-4"
|
|
||||||
state={submitState}
|
|
||||||
labels={{
|
|
||||||
default: buttonLabel,
|
|
||||||
pending: '',
|
|
||||||
}}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
/>
|
|
||||||
{!registrationEmbedded && (
|
|
||||||
<ThirdPartyAuth
|
|
||||||
currentProvider={currentProvider}
|
|
||||||
providers={providers}
|
|
||||||
secondaryProviders={secondaryProviders}
|
|
||||||
handleInstitutionLogin={handleInstitutionLogin}
|
|
||||||
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
|
||||||
/>
|
/>
|
||||||
)}
|
{!hideUsernameField && (
|
||||||
</Form>
|
<UsernameField
|
||||||
</div>
|
name="username"
|
||||||
)}
|
spellCheck="false"
|
||||||
|
value={formFields.username}
|
||||||
|
handleChange={handleOnChange}
|
||||||
|
handleErrorChange={handleErrorChange}
|
||||||
|
errorMessage={errors.username}
|
||||||
|
helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]}
|
||||||
|
floatingLabel={formatMessage(messages['registration.username.label'])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!currentProvider && (
|
||||||
|
<PasswordField
|
||||||
|
name="password"
|
||||||
|
value={formFields.password}
|
||||||
|
handleChange={handleOnChange}
|
||||||
|
handleErrorChange={handleErrorChange}
|
||||||
|
errorMessage={errors.password}
|
||||||
|
floatingLabel={formatMessage(messages['registration.password.label'])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ConfigurableRegistrationForm
|
||||||
|
email={formFields.email}
|
||||||
|
fieldErrors={errors}
|
||||||
|
formFields={configurableFormFields}
|
||||||
|
setFieldErrors={registrationEmbedded ? setTemporaryErrors : setErrors}
|
||||||
|
setFormFields={setConfigurableFormFields}
|
||||||
|
autoSubmitRegisterForm={autoSubmitRegForm}
|
||||||
|
fieldDescriptions={fieldDescriptions}
|
||||||
|
/>
|
||||||
|
<StatefulButton
|
||||||
|
id="register-user"
|
||||||
|
name="register-user"
|
||||||
|
type="submit"
|
||||||
|
variant="brand"
|
||||||
|
className="register-button mt-4 mb-4"
|
||||||
|
state={submitState}
|
||||||
|
labels={{
|
||||||
|
default: buttonLabel,
|
||||||
|
pending: '',
|
||||||
|
}}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
{!registrationEmbedded && (
|
||||||
|
<ThirdPartyAuth
|
||||||
|
currentProvider={currentProvider}
|
||||||
|
providers={providers}
|
||||||
|
secondaryProviders={secondaryProviders}
|
||||||
|
handleInstitutionLogin={handleInstitutionLogin}
|
||||||
|
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ import {
|
|||||||
setUserPipelineDataLoaded,
|
setUserPipelineDataLoaded,
|
||||||
} from './data/actions';
|
} from './data/actions';
|
||||||
import { INTERNAL_SERVER_ERROR } from './data/constants';
|
import { INTERNAL_SERVER_ERROR } from './data/constants';
|
||||||
|
import { NOT_INITIALIZED } from './data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation
|
||||||
|
from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
import RegistrationPage from './RegistrationPage';
|
import RegistrationPage from './RegistrationPage';
|
||||||
import {
|
import {
|
||||||
AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
APP_NAME, AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
@@ -30,6 +33,7 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
|
|||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
getLocale: jest.fn(),
|
getLocale: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('./data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
|
||||||
|
|
||||||
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
@@ -128,15 +132,23 @@ describe('RegistrationPage', () => {
|
|||||||
institutionLogin: false,
|
institutionLogin: false,
|
||||||
};
|
};
|
||||||
window.location = { search: '' };
|
window.location = { search: '' };
|
||||||
|
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
|
const populateRequiredFields = (
|
||||||
|
getByLabelText,
|
||||||
|
payload,
|
||||||
|
isThirdPartyAuth = false,
|
||||||
|
autoGeneratedUsernameEnabled = false,
|
||||||
|
) => {
|
||||||
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
|
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
|
||||||
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
|
if (!autoGeneratedUsernameEnabled) {
|
||||||
|
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
|
||||||
|
}
|
||||||
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
|
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
|
||||||
|
|
||||||
fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } });
|
fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } });
|
||||||
@@ -178,6 +190,7 @@ describe('RegistrationPage', () => {
|
|||||||
honor_code: true,
|
honor_code: true,
|
||||||
totalRegistrationTime: 0,
|
totalRegistrationTime: 0,
|
||||||
next: '/course/demo-course-url',
|
next: '/course/demo-course-url',
|
||||||
|
app_name: APP_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch = jest.fn(store.dispatch);
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
@@ -200,6 +213,7 @@ describe('RegistrationPage', () => {
|
|||||||
honor_code: true,
|
honor_code: true,
|
||||||
social_auth_provider: 'Apple',
|
social_auth_provider: 'Apple',
|
||||||
totalRegistrationTime: 0,
|
totalRegistrationTime: 0,
|
||||||
|
app_name: APP_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
store = mockStore({
|
store = mockStore({
|
||||||
@@ -285,6 +299,7 @@ describe('RegistrationPage', () => {
|
|||||||
honor_code: true,
|
honor_code: true,
|
||||||
totalRegistrationTime: 0,
|
totalRegistrationTime: 0,
|
||||||
marketing_emails_opt_in: true,
|
marketing_emails_opt_in: true,
|
||||||
|
app_name: APP_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch = jest.fn(store.dispatch);
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
@@ -299,6 +314,45 @@ describe('RegistrationPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should submit form without UsernameField when autoGeneratedUsernameEnabled is true', () => {
|
||||||
|
mergeConfig({
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME: true,
|
||||||
|
});
|
||||||
|
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||||
|
const payload = {
|
||||||
|
name: 'John Doe',
|
||||||
|
email: 'john.doe@gmail.com',
|
||||||
|
password: 'password1',
|
||||||
|
country: 'Pakistan',
|
||||||
|
honor_code: true,
|
||||||
|
totalRegistrationTime: 0,
|
||||||
|
app_name: APP_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
|
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||||
|
populateRequiredFields(getByLabelText, payload, false, true);
|
||||||
|
const button = container.querySelector('button.btn-brand');
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||||
|
mergeConfig({
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display UsernameField when ENABLE_AUTO_GENERATED_USERNAME is true', () => {
|
||||||
|
mergeConfig({
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||||
|
expect(queryByLabelText('Username')).toBeNull();
|
||||||
|
|
||||||
|
mergeConfig({
|
||||||
|
ENABLE_AUTO_GENERATED_USERNAME: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not dispatch registerNewUser on empty form Submission', () => {
|
it('should not dispatch registerNewUser on empty form Submission', () => {
|
||||||
store.dispatch = jest.fn(store.dispatch);
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
|
|
||||||
@@ -547,7 +601,7 @@ describe('RegistrationPage', () => {
|
|||||||
|
|
||||||
it('should send page event when register page is rendered', () => {
|
it('should send page event when register page is rendered', () => {
|
||||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
|
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register', { app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send track event when user has successfully registered', () => {
|
it('should send track event when user has successfully registered', () => {
|
||||||
@@ -565,7 +619,7 @@ describe('RegistrationPage', () => {
|
|||||||
delete window.location;
|
delete window.location;
|
||||||
window.location = { href: getConfig().BASE_URL };
|
window.location = { href: getConfig().BASE_URL };
|
||||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', {});
|
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', { app_name: APP_NAME });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate form with pipeline user details', () => {
|
it('should populate form with pipeline user details', () => {
|
||||||
@@ -838,6 +892,7 @@ describe('RegistrationPage', () => {
|
|||||||
country: 'PK',
|
country: 'PK',
|
||||||
social_auth_provider: 'Apple',
|
social_auth_provider: 'Apple',
|
||||||
totalRegistrationTime: 0,
|
totalRegistrationTime: 0,
|
||||||
|
app_name: APP_NAME,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { getCountryList, getLocale, useIntl } from '@edx/frontend-platform/i18n';
|
import { getCountryList, getLocale, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { FormFieldRenderer } from '../../field-renderer';
|
import { FormFieldRenderer } from '../../field-renderer';
|
||||||
|
import { backupRegistrationFormBegin } from '../data/actions';
|
||||||
import { FIELDS } from '../data/constants';
|
import { FIELDS } from '../data/constants';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { CountryField, HonorCode, TermsOfService } from '../RegistrationFields';
|
import { CountryField, HonorCode, TermsOfService } from '../RegistrationFields';
|
||||||
@@ -32,8 +34,13 @@ const ConfigurableRegistrationForm = (props) => {
|
|||||||
setFormFields,
|
setFormFields,
|
||||||
autoSubmitRegistrationForm,
|
autoSubmitRegistrationForm,
|
||||||
} = props;
|
} = props;
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const countryList = useMemo(() => getCountryList(getLocale()), []);
|
/** The reason for adding the entry 'United States' is that Chrome browser aut-fill the form with the 'Unites
|
||||||
|
States' instead of 'United States of America' which does not exist in country dropdown list and gets the user
|
||||||
|
confused and unable to create an account. So we added the United States entry in the dropdown list.
|
||||||
|
*/
|
||||||
|
const countryList = useMemo(() => getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []);
|
||||||
|
|
||||||
let showTermsOfServiceAndHonorCode = false;
|
let showTermsOfServiceAndHonorCode = false;
|
||||||
let showCountryField = false;
|
let showCountryField = false;
|
||||||
@@ -46,6 +53,8 @@ const ConfigurableRegistrationForm = (props) => {
|
|||||||
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const backedUpFormData = useSelector(state => state.register.registrationFormData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If auto submitting register form, we will check tos and honor code fields if they exist for feature parity.
|
* If auto submitting register form, we will check tos and honor code fields if they exist for feature parity.
|
||||||
*/
|
*/
|
||||||
@@ -86,6 +95,16 @@ const ConfigurableRegistrationForm = (props) => {
|
|||||||
setFieldErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
setFieldErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// setting marketingEmailsOptIn state for SSO authentication flow for register API call
|
||||||
|
if (name === 'marketingEmailsOptIn') {
|
||||||
|
dispatch(backupRegistrationFormBegin({
|
||||||
|
...backedUpFormData,
|
||||||
|
configurableFormFields: {
|
||||||
|
...backedUpFormData.configurableFormFields,
|
||||||
|
[name]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import { fireEvent, render } from '@testing-library/react';
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
import { APP_NAME } from '../../../data/constants';
|
||||||
import { registerNewUser } from '../../data/actions';
|
import { registerNewUser } from '../../data/actions';
|
||||||
import { FIELDS } from '../../data/constants';
|
import { FIELDS } from '../../data/constants';
|
||||||
|
import { NOT_INITIALIZED } from '../../data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation
|
||||||
|
from '../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
import RegistrationPage from '../../RegistrationPage';
|
import RegistrationPage from '../../RegistrationPage';
|
||||||
import ConfigurableRegistrationForm from '../ConfigurableRegistrationForm';
|
import ConfigurableRegistrationForm from '../ConfigurableRegistrationForm';
|
||||||
|
|
||||||
@@ -22,6 +26,7 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
|
|||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
getLocale: jest.fn(),
|
getLocale: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
|
||||||
|
|
||||||
const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
|
const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
|
||||||
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
||||||
@@ -121,6 +126,7 @@ describe('ConfigurableRegistrationForm', () => {
|
|||||||
};
|
};
|
||||||
window.location = { search: '' };
|
window.location = { search: '' };
|
||||||
getLocale.mockImplementationOnce(() => ('en-us'));
|
getLocale.mockImplementationOnce(() => ('en-us'));
|
||||||
|
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -260,7 +266,7 @@ describe('ConfigurableRegistrationForm', () => {
|
|||||||
|
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK', app_name: APP_NAME }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error messages for required fields on empty form submission', () => {
|
it('should show error messages for required fields on empty form submission', () => {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import configureStore from 'redux-mock-store';
|
|||||||
import {
|
import {
|
||||||
FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_AUTHENTICATION_FAILURE, TPA_SESSION_EXPIRED,
|
FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_AUTHENTICATION_FAILURE, TPA_SESSION_EXPIRED,
|
||||||
} from '../../data/constants';
|
} from '../../data/constants';
|
||||||
|
import { NOT_INITIALIZED } from '../../data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation
|
||||||
|
from '../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
import RegistrationPage from '../../RegistrationPage';
|
import RegistrationPage from '../../RegistrationPage';
|
||||||
import RegistrationFailureMessage from '../RegistrationFailure';
|
import RegistrationFailureMessage from '../RegistrationFailure';
|
||||||
|
|
||||||
@@ -23,6 +26,7 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
|
|||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
getLocale: jest.fn(),
|
getLocale: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
|
||||||
|
|
||||||
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
||||||
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
|
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
|
||||||
@@ -121,6 +125,7 @@ describe('RegistrationFailure', () => {
|
|||||||
institutionLogin: false,
|
institutionLogin: false,
|
||||||
};
|
};
|
||||||
window.location = { search: '' };
|
window.location = { search: '' };
|
||||||
|
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import configureStore from 'redux-mock-store';
|
|||||||
import {
|
import {
|
||||||
COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
||||||
} from '../../../data/constants';
|
} from '../../../data/constants';
|
||||||
|
import { NOT_INITIALIZED } from '../../data/optimizelyExperiment/helper';
|
||||||
|
import useAutoGeneratedUsernameExperimentVariation
|
||||||
|
from '../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
|
||||||
import RegistrationPage from '../../RegistrationPage';
|
import RegistrationPage from '../../RegistrationPage';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
@@ -22,6 +25,7 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
|
|||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
getLocale: jest.fn(),
|
getLocale: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
|
||||||
|
|
||||||
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
const IntlRegistrationPage = injectIntl(RegistrationPage);
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
@@ -120,6 +124,7 @@ describe('ThirdPartyAuth', () => {
|
|||||||
institutionLogin: false,
|
institutionLogin: false,
|
||||||
};
|
};
|
||||||
window.location = { search: '' };
|
window.location = { search: '' };
|
||||||
|
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const REGISTRATION_CLEAR_BACKEND_ERROR = 'REGISTRATION_CLEAR_BACKEND_ERRO
|
|||||||
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
|
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
|
||||||
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';
|
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';
|
||||||
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
|
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
|
||||||
|
export const REGISTER_SET_AUTO_GENERATED_USERNAME_REGISTRATION_EXP_DATA = 'REGISTER_SET_AUTO_GENERATED_USERNAME_REGISTRATION_EXP_DATA';
|
||||||
// Backup registration form
|
// Backup registration form
|
||||||
export const backupRegistrationForm = () => ({
|
export const backupRegistrationForm = () => ({
|
||||||
type: BACKUP_REGISTRATION_DATA.BASE,
|
type: BACKUP_REGISTRATION_DATA.BASE,
|
||||||
@@ -83,3 +83,9 @@ export const setUserPipelineDataLoaded = (value) => ({
|
|||||||
type: REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
type: REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||||
payload: { value },
|
payload: { value },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Auto Generated Username Registration Experiment Actions
|
||||||
|
export const setAutoGeneratedUsernameExperimentData = (autoGeneratedRegExpVariation) => ({
|
||||||
|
type: REGISTER_SET_AUTO_GENERATED_USERNAME_REGISTRATION_EXP_DATA,
|
||||||
|
payload: { autoGeneratedRegExpVariation },
|
||||||
|
});
|
||||||
|
|||||||
30
src/register/data/optimizelyExperiment/helper.js
Normal file
30
src/register/data/optimizelyExperiment/helper.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* This file contains data for auto generated username Optimizely experiment
|
||||||
|
*/
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
|
export const NOT_INITIALIZED = 'experiment-not-initialized';
|
||||||
|
export const CONTROL = 'control-registration-page';
|
||||||
|
export const AUTO_GENERATED_USERNAME_REGISTRATION_EXP_VARIATION = 'auto-generated-username-register-page';
|
||||||
|
const AUTO_GENERATED_USERNAME_EXP_PAGE = 'targeting_for_auto_generated_username_page';
|
||||||
|
|
||||||
|
export function getAutoGeneratedUsernameExperimentVariation() {
|
||||||
|
try {
|
||||||
|
if (window.optimizely
|
||||||
|
&& window.optimizely.get('data').experiments[getConfig().AUTO_GENERATED_USERNAME_EXPERIMENT_ID]) {
|
||||||
|
const selectedVariant = window.optimizely.get('state').getVariationMap()[
|
||||||
|
getConfig().AUTO_GENERATED_USERNAME_EXPERIMENT_ID
|
||||||
|
];
|
||||||
|
return selectedVariant?.name;
|
||||||
|
}
|
||||||
|
} catch (e) { /* empty */ }
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activateAutoGeneratedUsernameExperiment() {
|
||||||
|
window.optimizely = window.optimizely || [];
|
||||||
|
window.optimizely.push({
|
||||||
|
type: 'page',
|
||||||
|
pageName: AUTO_GENERATED_USERNAME_EXP_PAGE,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
activateAutoGeneratedUsernameExperiment,
|
||||||
|
getAutoGeneratedUsernameExperimentVariation,
|
||||||
|
NOT_INITIALIZED,
|
||||||
|
} from './helper';
|
||||||
|
import { COMPLETE_STATE } from '../../../data/constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This hook returns activates multi step registration experiment and returns the experiment
|
||||||
|
* variation for the user.
|
||||||
|
*/
|
||||||
|
const useAutoGeneratedUsernameExperimentVariation = (
|
||||||
|
initExpVariation,
|
||||||
|
registrationEmbedded,
|
||||||
|
tpaHint,
|
||||||
|
currentProvider,
|
||||||
|
thirdPartyAuthApiStatus,
|
||||||
|
) => {
|
||||||
|
const [variation, setVariation] = useState(initExpVariation);
|
||||||
|
useEffect(() => {
|
||||||
|
if (initExpVariation || registrationEmbedded || !!tpaHint || !!currentProvider
|
||||||
|
|| thirdPartyAuthApiStatus !== COMPLETE_STATE) {
|
||||||
|
return variation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVariation = () => {
|
||||||
|
const expVariation = getAutoGeneratedUsernameExperimentVariation();
|
||||||
|
if (expVariation) {
|
||||||
|
setVariation(expVariation);
|
||||||
|
} else {
|
||||||
|
// This is to handle the case when user dont get variation for some reason, the register page
|
||||||
|
// shows unlimited spinner.
|
||||||
|
setVariation(NOT_INITIALIZED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
activateAutoGeneratedUsernameExperiment();
|
||||||
|
|
||||||
|
const timer = setTimeout(getVariation, 300);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [ // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
initExpVariation, currentProvider, registrationEmbedded, thirdPartyAuthApiStatus, tpaHint,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return variation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAutoGeneratedUsernameExperimentVariation;
|
||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
||||||
REGISTER_FORM_VALIDATIONS,
|
REGISTER_FORM_VALIDATIONS,
|
||||||
REGISTER_NEW_USER,
|
REGISTER_NEW_USER,
|
||||||
|
REGISTER_SET_AUTO_GENERATED_USERNAME_REGISTRATION_EXP_DATA,
|
||||||
REGISTER_SET_COUNTRY_CODE,
|
REGISTER_SET_COUNTRY_CODE,
|
||||||
REGISTER_SET_EMAIL_SUGGESTIONS,
|
REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||||
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||||
@@ -39,6 +40,7 @@ export const defaultState = {
|
|||||||
usernameSuggestions: [],
|
usernameSuggestions: [],
|
||||||
validationApiRateLimited: false,
|
validationApiRateLimited: false,
|
||||||
shouldBackupState: false,
|
shouldBackupState: false,
|
||||||
|
autoGeneratedUsernameExperimentVariation: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = (state = defaultState, action = {}) => {
|
const reducer = (state = defaultState, action = {}) => {
|
||||||
@@ -55,6 +57,12 @@ const reducer = (state = defaultState, action = {}) => {
|
|||||||
registrationFormData: { ...action.payload },
|
registrationFormData: { ...action.payload },
|
||||||
userPipelineDataLoaded: state.userPipelineDataLoaded,
|
userPipelineDataLoaded: state.userPipelineDataLoaded,
|
||||||
};
|
};
|
||||||
|
case REGISTER_SET_AUTO_GENERATED_USERNAME_REGISTRATION_EXP_DATA: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autoGeneratedUsernameExperimentVariation: action.payload.autoGeneratedRegExpVariation,
|
||||||
|
};
|
||||||
|
}
|
||||||
case REGISTER_NEW_USER.BEGIN:
|
case REGISTER_NEW_USER.BEGIN:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ describe('Registration Reducer Tests', () => {
|
|||||||
usernameSuggestions: [],
|
usernameSuggestions: [],
|
||||||
validationApiRateLimited: false,
|
validationApiRateLimited: false,
|
||||||
shouldBackupState: false,
|
shouldBackupState: false,
|
||||||
|
autoGeneratedUsernameExperimentVariation: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should return the initial state', () => {
|
it('should return the initial state', () => {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const isFormValid = (
|
|||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
emailSuggestion = suggestion;
|
emailSuggestion = suggestion;
|
||||||
|
if (fieldErrors.email) { isValid = false; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'username':
|
case 'username':
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { resetPassword, validateToken } from './data/actions';
|
import { resetPassword, validateToken } from './data/actions';
|
||||||
import {
|
import {
|
||||||
FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, TOKEN_STATE,
|
FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, SUCCESS, TOKEN_STATE,
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
import { resetPasswordResultSelector } from './data/selectors';
|
import { resetPasswordResultSelector } from './data/selectors';
|
||||||
import { validatePassword } from './data/service';
|
import { validatePassword } from './data/service';
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE,
|
LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||||
|
import { trackPasswordResetSuccess, trackResetPasswordPageViewed } from '../tracking/trackers/reset-password';
|
||||||
|
|
||||||
const ResetPasswordPage = (props) => {
|
const ResetPasswordPage = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -42,6 +43,15 @@ const ResetPasswordPage = (props) => {
|
|||||||
const { token } = useParams();
|
const { token } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.status === TOKEN_STATE.VALID) {
|
||||||
|
trackResetPasswordPageViewed();
|
||||||
|
}
|
||||||
|
if (props.status === SUCCESS) {
|
||||||
|
trackPasswordResetSuccess();
|
||||||
|
}
|
||||||
|
}, [props.status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.status !== TOKEN_STATE.PENDING && props.status !== PASSWORD_RESET_ERROR) {
|
if (props.status !== TOKEN_STATE.PENDING && props.status !== PASSWORD_RESET_ERROR) {
|
||||||
setErrorCode(props.status);
|
setErrorCode(props.status);
|
||||||
@@ -139,7 +149,7 @@ const ResetPasswordPage = (props) => {
|
|||||||
}
|
}
|
||||||
} else if (props.status === PASSWORD_RESET_ERROR) {
|
} else if (props.status === PASSWORD_RESET_ERROR) {
|
||||||
navigate(updatePathWithQueryParams(RESET_PAGE));
|
navigate(updatePathWithQueryParams(RESET_PAGE));
|
||||||
} else if (props.status === 'success') {
|
} else if (props.status === SUCCESS) {
|
||||||
navigate(updatePathWithQueryParams(LOGIN_PAGE));
|
navigate(updatePathWithQueryParams(LOGIN_PAGE));
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ import ResetPasswordPage from '../ResetPasswordPage';
|
|||||||
const mockedNavigator = jest.fn();
|
const mockedNavigator = jest.fn();
|
||||||
const token = '1c-bmjdkc-5e60e084cf8113048ca7';
|
const token = '1c-bmjdkc-5e60e084cf8113048ca7';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
|
sendPageEvent: jest.fn(),
|
||||||
|
sendTrackEvent: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...(jest.requireActual('react-router-dom')),
|
...(jest.requireActual('react-router-dom')),
|
||||||
|
|||||||
22
src/tracking/trackers/forgotpassword.js
Normal file
22
src/tracking/trackers/forgotpassword.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../data/segment/utils';
|
||||||
|
|
||||||
|
export const eventNames = {
|
||||||
|
loginAndRegistration: 'login_and_registration',
|
||||||
|
forgotPasswordPageViewed: 'edx.bi.password_reset_form.viewed',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const categories = {
|
||||||
|
userEngagement: 'user-engagement',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event tracker for forgot password page viewed
|
||||||
|
export const trackForgotPasswordPageViewed = () => createEventTracker(
|
||||||
|
eventNames.forgotPasswordPageViewed,
|
||||||
|
{
|
||||||
|
category: categories.userEngagement,
|
||||||
|
},
|
||||||
|
)();
|
||||||
|
|
||||||
|
export const trackForgotPasswordPageEvent = () => {
|
||||||
|
createPageEventTracker(eventNames.loginAndRegistration, 'forgot-password')();
|
||||||
|
};
|
||||||
29
src/tracking/trackers/login.js
Normal file
29
src/tracking/trackers/login.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../data/segment/utils';
|
||||||
|
|
||||||
|
export const eventNames = {
|
||||||
|
forgotPasswordLinkClicked: 'edx.bi.password-reset_form.toggled',
|
||||||
|
loginAndRegistration: 'login_and_registration',
|
||||||
|
registerFormToggled: 'edx.bi.register_form.toggled',
|
||||||
|
loginSuccess: 'edx.bi.user.account.authenticated.client',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const categories = {
|
||||||
|
userEngagement: 'user-engagement',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event tracker for Forgot Password link click
|
||||||
|
export const trackForgotPasswordLinkClick = () => createEventTracker(
|
||||||
|
eventNames.forgotPasswordLinkClicked,
|
||||||
|
{ category: categories.userEngagement },
|
||||||
|
)();
|
||||||
|
|
||||||
|
// Tracks the login page event.
|
||||||
|
export const trackLoginPageViewed = () => {
|
||||||
|
createPageEventTracker(eventNames.loginAndRegistration, 'login')();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tracks the login sucess event.
|
||||||
|
export const trackLoginSuccess = () => createEventTracker(
|
||||||
|
eventNames.loginSuccess,
|
||||||
|
{},
|
||||||
|
)();
|
||||||
37
src/tracking/trackers/progressive-profiling.js
Normal file
37
src/tracking/trackers/progressive-profiling.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../data/segment/utils';
|
||||||
|
|
||||||
|
export const eventNames = {
|
||||||
|
progressiveProfilingSubmitClick: 'edx.bi.welcome.page.submit.clicked',
|
||||||
|
progressiveProfilingSkipLinkClick: 'edx.bi.welcome.page.skip.link.clicked',
|
||||||
|
disablePostRegistrationRecommendations: 'edx.bi.user.recommendations.not.enabled',
|
||||||
|
progressiveProfilingSupportLinkCLick: 'edx.bi.welcome.page.support.link.clicked',
|
||||||
|
loginAndRegistration: 'login_and_registration',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event link tracker for Progressive profiling skip button click
|
||||||
|
export const trackProgressiveProfilingSkipLinkClick = evenProperties => createEventTracker(
|
||||||
|
eventNames.progressiveProfilingSkipLinkClick, { ...evenProperties },
|
||||||
|
)();
|
||||||
|
|
||||||
|
// Event tracker for progressive profiling submit button click
|
||||||
|
export const trackProgressiveProfilingSubmitClick = (evenProperties) => createEventTracker(
|
||||||
|
eventNames.progressiveProfilingSubmitClick,
|
||||||
|
{ ...evenProperties },
|
||||||
|
)();
|
||||||
|
|
||||||
|
// Event tracker for progressive profiling submit button click
|
||||||
|
export const trackDisablePostRegistrationRecommendations = (evenProperties) => createEventTracker(
|
||||||
|
eventNames.disablePostRegistrationRecommendations,
|
||||||
|
{ ...evenProperties },
|
||||||
|
)();
|
||||||
|
|
||||||
|
// Tracks the progressive profiling page event.
|
||||||
|
export const trackProgressiveProfilingPageViewed = () => {
|
||||||
|
createPageEventTracker(eventNames.loginAndRegistration, 'welcome')();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tracks the progressive profiling spport link click.
|
||||||
|
export const trackProgressiveProfilingSupportLinkCLick = () => createEventTracker(
|
||||||
|
eventNames.progressiveProfilingSupportLinkCLick,
|
||||||
|
{},
|
||||||
|
)();
|
||||||
22
src/tracking/trackers/register.js
Normal file
22
src/tracking/trackers/register.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../data/segment/utils';
|
||||||
|
|
||||||
|
export const eventNames = {
|
||||||
|
loginAndRegistration: 'login_and_registration',
|
||||||
|
registrationSuccess: 'edx.bi.user.account.registered.client',
|
||||||
|
loginFormToggled: 'edx.bi.login_form.toggled',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const categories = {
|
||||||
|
userEngagement: 'user-engagement',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event tracker for successful registration
|
||||||
|
export const trackRegistrationSuccess = () => createEventTracker(
|
||||||
|
eventNames.registrationSuccess,
|
||||||
|
{},
|
||||||
|
)();
|
||||||
|
|
||||||
|
// Tracks the progressive profiling page event.
|
||||||
|
export const trackRegistrationPageViewed = () => {
|
||||||
|
createPageEventTracker(eventNames.loginAndRegistration, 'register')();
|
||||||
|
};
|
||||||
14
src/tracking/trackers/reset-password.js
Normal file
14
src/tracking/trackers/reset-password.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../data/segment/utils';
|
||||||
|
|
||||||
|
export const eventNames = {
|
||||||
|
loginAndRegistration: 'login_and_registration',
|
||||||
|
resetPasswordSuccess: 'edx.bi.user.password.reset.success',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackResetPasswordPageViewed = () => {
|
||||||
|
createPageEventTracker(eventNames.loginAndRegistration, 'reset-password')();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackPasswordResetSuccess = () => {
|
||||||
|
createEventTracker(eventNames.resetPasswordSuccess, {})();
|
||||||
|
};
|
||||||
37
src/tracking/trackers/tests/forgot-password.test.jsx
Normal file
37
src/tracking/trackers/tests/forgot-password.test.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils';
|
||||||
|
import {
|
||||||
|
categories,
|
||||||
|
eventNames,
|
||||||
|
trackForgotPasswordPageEvent,
|
||||||
|
trackForgotPasswordPageViewed,
|
||||||
|
} from '../forgotpassword';
|
||||||
|
|
||||||
|
// Mock createEventTracker function
|
||||||
|
jest.mock('../../../data/segment/utils', () => ({
|
||||||
|
createEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tracking Functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire trackForgotPasswordPageEvent', () => {
|
||||||
|
trackForgotPasswordPageEvent();
|
||||||
|
|
||||||
|
expect(createPageEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.loginAndRegistration,
|
||||||
|
'forgot-password',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire forgotPasswordPageViewedEvent', () => {
|
||||||
|
trackForgotPasswordPageViewed();
|
||||||
|
|
||||||
|
expect(createEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.forgotPasswordPageViewed,
|
||||||
|
{ category: categories.userEngagement },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
37
src/tracking/trackers/tests/login.test.jsx
Normal file
37
src/tracking/trackers/tests/login.test.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils';
|
||||||
|
import {
|
||||||
|
categories,
|
||||||
|
eventNames,
|
||||||
|
trackForgotPasswordLinkClick,
|
||||||
|
trackLoginPageViewed,
|
||||||
|
} from '../login';
|
||||||
|
|
||||||
|
// Mock createEventTracker function
|
||||||
|
jest.mock('../../../data/segment/utils', () => ({
|
||||||
|
createEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tracking Functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trackForgotPasswordLinkClick function', () => {
|
||||||
|
trackForgotPasswordLinkClick();
|
||||||
|
|
||||||
|
expect(createEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.forgotPasswordLinkClicked,
|
||||||
|
{ category: categories.userEngagement },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trackLoginPageEvent function', () => {
|
||||||
|
trackLoginPageViewed();
|
||||||
|
|
||||||
|
expect(createPageEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.loginAndRegistration,
|
||||||
|
'login',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
37
src/tracking/trackers/tests/progressive-profiling.test.jsx
Normal file
37
src/tracking/trackers/tests/progressive-profiling.test.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils';
|
||||||
|
import {
|
||||||
|
eventNames,
|
||||||
|
trackProgressiveProfilingPageViewed,
|
||||||
|
trackProgressiveProfilingSkipLinkClick,
|
||||||
|
} from '../progressive-profiling';
|
||||||
|
|
||||||
|
// Mock createEventTracker function
|
||||||
|
jest.mock('../../../data/segment/utils', () => ({
|
||||||
|
createEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createLinkTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tracking Functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire trackProgressiveProfilingSkipLinkClickEvent', () => {
|
||||||
|
trackProgressiveProfilingSkipLinkClick();
|
||||||
|
|
||||||
|
expect(createEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.progressiveProfilingSkipLinkClick,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire trackProgressiveProfilingPageEvent', () => {
|
||||||
|
trackProgressiveProfilingPageViewed();
|
||||||
|
|
||||||
|
expect(createPageEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.loginAndRegistration,
|
||||||
|
'welcome',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
36
src/tracking/trackers/tests/register.test.jsx
Normal file
36
src/tracking/trackers/tests/register.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils';
|
||||||
|
import {
|
||||||
|
eventNames,
|
||||||
|
trackRegistrationPageViewed,
|
||||||
|
trackRegistrationSuccess,
|
||||||
|
} from '../register';
|
||||||
|
|
||||||
|
// Mock createEventTracker function
|
||||||
|
jest.mock('../../../data/segment/utils', () => ({
|
||||||
|
createEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tracking Functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire registrationSuccessEvent', () => {
|
||||||
|
trackRegistrationSuccess();
|
||||||
|
|
||||||
|
expect(createEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.registrationSuccess,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire trackRegistrationPageEvent', () => {
|
||||||
|
trackRegistrationPageViewed();
|
||||||
|
|
||||||
|
expect(createPageEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.loginAndRegistration,
|
||||||
|
'register',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/tracking/trackers/tests/reset-password.test.jsx
Normal file
26
src/tracking/trackers/tests/reset-password.test.jsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { createPageEventTracker } from '../../../data/segment/utils';
|
||||||
|
import {
|
||||||
|
eventNames,
|
||||||
|
trackResetPasswordPageViewed,
|
||||||
|
} from '../reset-password';
|
||||||
|
|
||||||
|
// Mock createEventTracker function
|
||||||
|
jest.mock('../../../data/segment/utils', () => ({
|
||||||
|
createEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tracking Functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire trackResettPasswordPageEvent', () => {
|
||||||
|
trackResetPasswordPageViewed();
|
||||||
|
|
||||||
|
expect(createPageEventTracker).toHaveBeenCalledWith(
|
||||||
|
eventNames.loginAndRegistration,
|
||||||
|
'reset-password',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user