From c2d9a9384dcff641d995f54976740e5d90a855c6 Mon Sep 17 00:00:00 2001 From: Waheed Ahmed Date: Thu, 11 Mar 2021 20:01:16 +0500 Subject: [PATCH] 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 --- package-lock.json | 21 +++++++++++++----- package.json | 11 +++++----- src/data/constants.js | 4 ++++ src/data/utils/dataUtils.js | 25 ++++++++++++++++++++++ src/data/utils/dataUtils.test.js | 20 ++++++++++++++++- src/data/utils/index.js | 8 ++++++- src/forgot-password/ForgotPasswordPage.jsx | 3 ++- src/login/LoginHelpLinks.jsx | 5 +++-- src/login/LoginPage.jsx | 24 ++++++++++----------- src/register/RegistrationPage.jsx | 23 ++++++++++---------- 10 files changed, 105 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index b41cab14..c5409cbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } }, diff --git a/package.json b/package.json index 9e146ad1..c5f00f3e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/data/constants.js b/src/data/constants.js index 19e63d56..a8e1342e 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -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']; diff --git a/src/data/utils/dataUtils.js b/src/data/utils/dataUtils.js index 05bc8752..0963afa7 100644 --- a/src/data/utils/dataUtils.js +++ b/src/data/utils/dataUtils.js @@ -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>(.*)/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; +}; diff --git a/src/data/utils/dataUtils.test.js b/src/data/utils/dataUtils.test.js index d37e31f2..b2dd3c58 100644 --- a/src/data/utils/dataUtils.test.js +++ b/src/data/utils/dataUtils.test.js @@ -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); + }); +}); diff --git a/src/data/utils/index.js b/src/data/utils/index.js index 96fe6c27..6719e5ef 100644 --- a/src/data/utils/index.js +++ b/src/data/utils/index.js @@ -1,2 +1,8 @@ -export { default, getTpaProvider, processTpaHintURL } from './dataUtils'; +export { + default, + getTpaProvider, + processTpaHintURL, + updatePathWithQueryParams, + getAllPossibleQueryParam, +} from './dataUtils'; export { default as AsyncActionType } from './reduxUtils'; diff --git a/src/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx index 50e376d5..5c737a40 100644 --- a/src/forgot-password/ForgotPasswordPage.jsx +++ b/src/forgot-password/ForgotPasswordPage.jsx @@ -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 })} - {status === 'complete' ? : null} + {status === 'complete' ? : null}
diff --git a/src/login/LoginHelpLinks.jsx b/src/login/LoginHelpLinks.jsx index 31f179bc..bba14ed5 100644 --- a/src/login/LoginHelpLinks.jsx +++ b/src/login/LoginHelpLinks.jsx @@ -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 = () => ( {intl.formatMessage(messages['forgot.password.link'])} @@ -39,7 +40,7 @@ const LoginHelpLinks = (props) => { ); const signUpLink = () => ( - + {intl.formatMessage(messages['register.link'])} ); diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 8a36880b..fed46eb0 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -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}

{intl.formatMessage(messages['first.time.here'])} - + {intl.formatMessage(messages['create.an.account'])}.

diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 48514326..5d5dd119 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -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 { )}

{intl.formatMessage(messages['already.have.an.edx.account'])} - + {intl.formatMessage(messages['sign.in.hyperlink'])}