Keep the query params intact within the app. (#201)

Currently, if the user lands on one page with course_id and enroll action
params and navigates to another page with the MFE, the query params
are not passed to that other page and resulting in not enrolling into
the course.

Also add these query params into login and register payload.

VAN-415
This commit is contained in:
Waheed Ahmed
2021-03-11 20:01:16 +05:00
committed by GitHub
parent daae34dab6
commit c2d9a9384d
10 changed files with 105 additions and 39 deletions

21
package-lock.json generated
View File

@@ -9704,8 +9704,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"decompress": {
"version": "4.2.1",
@@ -17665,6 +17664,16 @@
"cast-array": "~1.0.0",
"object-filter": "~1.0.2",
"query-string": "~2.4.1"
},
"dependencies": {
"query-string": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-2.4.2.tgz",
"integrity": "sha1-fbBmZCCAS6qSrp8miWKFWnYUPfs=",
"requires": {
"strict-uri-encode": "^1.0.0"
}
}
}
},
"make-dir": {
@@ -20685,10 +20694,12 @@
"dev": true
},
"query-string": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-2.4.2.tgz",
"integrity": "sha1-fbBmZCCAS6qSrp8miWKFWnYUPfs=",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
"requires": {
"decode-uri-component": "^0.2.0",
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
},

View File

@@ -35,25 +35,27 @@
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-cookie-policy-banner": "2.1.8",
"@edx/frontend-component-header": "2.2.4",
"@edx/frontend-platform": "1.8.4",
"@edx/frontend-component-cookie-policy-banner": "2.1.8",
"@edx/paragon": "13.16.1",
"@fortawesome/fontawesome-svg-core": "1.2.32",
"@fortawesome/free-brands-svg-icons": "5.15.1",
"@fortawesome/free-regular-svg-icons": "5.15.1",
"@fortawesome/free-solid-svg-icons": "5.15.1",
"@fortawesome/react-fontawesome": "0.1.13",
"core-js": "3.9.1",
"classnames": "2.2.6",
"core-js": "3.9.1",
"extract-react-intl-messages": "4.1.1",
"form-urlencoded": "4.2.1",
"formik": "2.2.6",
"lodash.camelcase": "4.3.0",
"lodash.snakecase": "4.1.1",
"prop-types": "15.7.2",
"query-string": "5.1.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "6.1.0",
"react-loading-skeleton": "2.1.1",
"react-redux": "7.2.2",
"react-router": "5.2.0",
@@ -64,9 +66,8 @@
"redux-mock-store": "1.5.4",
"redux-saga": "1.1.3",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"react-helmet": "6.1.0",
"regenerator-runtime": "0.13.7"
"regenerator-runtime": "0.13.7",
"reselect": "4.0.0"
},
"devDependencies": {
"@edx/frontend-build": "5.6.8",

View File

@@ -24,3 +24,7 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
// Query string parameters that can be passed to LMS to manage
// things like auto-enrollment upon login and registration.
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next'];

View File

@@ -1,5 +1,8 @@
// Utility functions
import * as QueryString from 'query-string';
import { AUTH_PARAMS } from '../constants';
export default function processLink(link) {
let matches;
link.replace(/(.*?)<a href=["']([^"']*).*?>([^<]+)<\/a>(.*)/g, function () { // eslint-disable-line func-names
@@ -40,3 +43,25 @@ export const processTpaHintURL = (params) => {
}
return tpaHint;
};
export const updatePathWithQueryParams = (path) => {
const queryParams = window.location.search;
if (!queryParams) {
return path;
}
return `${path}${queryParams}`;
};
export const getAllPossibleQueryParam = () => {
const urlParams = QueryString.parse(document.location.search);
const params = {};
Object.entries(urlParams).forEach(([key, value]) => {
if (AUTH_PARAMS.indexOf(key) > -1) {
params[key] = value;
}
});
return params;
};

View File

@@ -1,4 +1,5 @@
import processLink from './dataUtils';
import { LOGIN_PAGE } from '../constants';
import processLink, { updatePathWithQueryParams } from './dataUtils';
describe('processLink', () => {
it('should use the provided processLink function to', () => {
@@ -12,3 +13,20 @@ describe('processLink', () => {
expect(matches[2]).toEqual(expectedText);
});
});
describe('updatePathWithQueryParams', () => {
it('should append query params into the path', () => {
const params = '?course_id=testCourseId';
const expectedPath = `${LOGIN_PAGE}${params}`;
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost/',
search: params,
},
});
const updatedPath = updatePathWithQueryParams(LOGIN_PAGE);
expect(updatedPath).toEqual(expectedPath);
});
});

View File

@@ -1,2 +1,8 @@
export { default, getTpaProvider, processTpaHintURL } from './dataUtils';
export {
default,
getTpaProvider,
processTpaHintURL,
updatePathWithQueryParams,
getAllPossibleQueryParam,
} from './dataUtils';
export { default as AsyncActionType } from './reduxUtils';

View File

@@ -28,6 +28,7 @@ import {
import APIFailureMessage from '../common-components/APIFailureMessage';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import LoginHelpLinks from '../login/LoginHelpLinks';
import { updatePathWithQueryParams } from '../data/utils';
const ForgotPasswordPage = (props) => {
const { intl, status } = props;
@@ -92,7 +93,7 @@ const ForgotPasswordPage = (props) => {
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
{status === 'complete' ? <Redirect to={LOGIN_PAGE} /> : null}
{status === 'complete' ? <Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} /> : null}
<div className="d-flex justify-content-center m-4">
<div className="d-flex flex-column">
<Form className="mw-500">

View File

@@ -14,6 +14,7 @@ import {
RESET_PAGE,
} from '../data/constants';
import messages from './messages';
import { updatePathWithQueryParams } from '../data/utils';
const LoginHelpLinks = (props) => {
const { intl, page } = props;
@@ -31,7 +32,7 @@ const LoginHelpLinks = (props) => {
const forgotPasswordLink = () => (
<Hyperlink
className="field-link"
destination={RESET_PAGE}
destination={updatePathWithQueryParams(RESET_PAGE)}
onClick={handleForgotPasswordLinkClickEvent}
>
{intl.formatMessage(messages['forgot.password.link'])}
@@ -39,7 +40,7 @@ const LoginHelpLinks = (props) => {
);
const signUpLink = () => (
<Hyperlink className="field-link" destination={REGISTER_PAGE}>
<Hyperlink className="field-link" destination={updatePathWithQueryParams(REGISTER_PAGE)}>
{intl.formatMessage(messages['register.link'])}
</Hyperlink>
);

View File

@@ -32,7 +32,9 @@ import {
DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, PENDING_STATE,
} from '../data/constants';
import { forgotPasswordResultSelector } from '../forgot-password';
import { getTpaProvider, processTpaHintURL } from '../data/utils';
import {
getTpaProvider, processTpaHintURL, updatePathWithQueryParams, getAllPossibleQueryParam,
} from '../data/utils';
class LoginPage extends React.Component {
constructor(props, context) {
@@ -90,16 +92,10 @@ class LoginPage extends React.Component {
return;
}
const params = (new URL(document.location)).searchParams;
const payload = { email, password };
const next = params.get('next');
const courseId = params.get('course_id');
if (next) {
payload.next = next;
}
if (courseId) {
payload.course_id = courseId;
}
let payload = { email, password };
const postParams = getAllPossibleQueryParam();
payload = { ...payload, ...postParams };
this.props.loginRequest(payload);
}
@@ -203,7 +199,11 @@ class LoginPage extends React.Component {
) : null}
<p>
{intl.formatMessage(messages['first.time.here'])}
<Hyperlink className="ml-1" destination={REGISTER_PAGE} onClick={this.handleCreateAccountLinkClickEvent}>
<Hyperlink
className="ml-1"
destination={updatePathWithQueryParams(REGISTER_PAGE)}
onClick={this.handleCreateAccountLinkClickEvent}
>
{intl.formatMessage(messages['create.an.account'])}.
</Hyperlink>
</p>

View File

@@ -31,7 +31,9 @@ import EnterpriseSSO from '../common-components/EnterpriseSSO';
import {
DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
} from '../data/constants';
import { getTpaProvider, processTpaHintURL } from '../data/utils';
import {
getTpaProvider, processTpaHintURL, updatePathWithQueryParams, getAllPossibleQueryParam,
} from '../data/utils';
class RegistrationPage extends React.Component {
constructor(props, context) {
@@ -153,8 +155,7 @@ class RegistrationPage extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
const params = (new URL(document.location)).searchParams;
const payload = {
let payload = {
name: this.state.name,
username: this.state.username,
email: this.state.email,
@@ -168,14 +169,8 @@ class RegistrationPage extends React.Component {
payload.password = this.state.password;
}
const next = params.get('next');
const courseId = params.get('course_id');
if (next) {
payload.next = next;
}
if (courseId) {
payload.course_id = courseId;
}
const postParams = getAllPossibleQueryParam();
payload = { ...payload, ...postParams };
let finalValidation = this.state.formValid;
if (!this.state.formValid) {
@@ -461,7 +456,11 @@ class RegistrationPage extends React.Component {
)}
<p>
{intl.formatMessage(messages['already.have.an.edx.account'])}
<Hyperlink className="ml-1" destination={LOGIN_PAGE} onClick={this.handleLoginLinkClickEvent}>
<Hyperlink
className="ml-1"
destination={updatePathWithQueryParams(LOGIN_PAGE)}
onClick={this.handleLoginLinkClickEvent}
>
{intl.formatMessage(messages['sign.in.hyperlink'])}
</Hyperlink>
</p>