diff --git a/package-lock.json b/package-lock.json index ba577677..94360c2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "lodash.camelcase": "4.3.0", "postcss-loader": "^8.1.1", "prop-types": "15.8.1", - "query-string": "^7.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet": "6.1.0", @@ -18318,24 +18317,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", diff --git a/package.json b/package.json index cc10088f..63f57520 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "lodash.camelcase": "4.3.0", "postcss-loader": "^8.1.1", "prop-types": "15.8.1", - "query-string": "^7.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet": "6.1.0", diff --git a/src/courseware/RedirectPage.test.jsx b/src/courseware/RedirectPage.test.jsx index 604e6f76..97f622a9 100644 --- a/src/courseware/RedirectPage.test.jsx +++ b/src/courseware/RedirectPage.test.jsx @@ -16,6 +16,7 @@ jest.mock('react-router-dom', () => ({ useLocation: () => ({ search: '?consentPath=/some-path', }), + useSearchParams: () => [new URLSearchParams('?consentPath=/some-path'), () => {}], })); describe('RedirectPage component', () => { diff --git a/src/courseware/RedirectPage.jsx b/src/courseware/RedirectPage.tsx similarity index 72% rename from src/courseware/RedirectPage.jsx rename to src/courseware/RedirectPage.tsx index 315d9186..0a84b3ac 100644 --- a/src/courseware/RedirectPage.jsx +++ b/src/courseware/RedirectPage.tsx @@ -1,18 +1,20 @@ -import PropTypes from 'prop-types'; import { - generatePath, useParams, useLocation, + generatePath, useParams, useLocation, useSearchParams, } from 'react-router-dom'; import { getConfig } from '@edx/frontend-platform'; -import queryString from 'query-string'; import { REDIRECT_MODES } from '../constants'; -const RedirectPage = ({ - pattern, mode, -}) => { +interface Props { + pattern: string; + mode: string; +} + +const RedirectPage = ({ pattern = '', mode }: Props) => { const { courseId } = useParams(); const location = useLocation(); - const { consentPath } = queryString.parse(location?.search); + const [searchParams] = useSearchParams(); + const consentPath = searchParams.get('consentPath') ?? ''; const { LMS_BASE_URL, @@ -39,13 +41,4 @@ const RedirectPage = ({ return null; }; -RedirectPage.propTypes = { - pattern: PropTypes.string, - mode: PropTypes.string.isRequired, -}; - -RedirectPage.defaultProps = { - pattern: null, -}; - export default RedirectPage; diff --git a/src/courseware/course/sequence/Unit/urls.js b/src/courseware/course/sequence/Unit/urls.js deleted file mode 100644 index d13fe6e3..00000000 --- a/src/courseware/course/sequence/Unit/urls.js +++ /dev/null @@ -1,35 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { stringifyUrl } from 'query-string'; - -export const iframeParams = { - show_title: 0, - show_bookmark: 0, - recheck_access: 1, -}; - -export const getIFrameUrl = ({ - id, - view, - format, - examAccess, - jumpToId, - preview, -}) => { - const xblockUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}`; - return stringifyUrl({ - url: xblockUrl, - query: { - ...iframeParams, - view, - preview, - ...(format && { format }), - ...(!examAccess.blockAccess && { exam_access: examAccess.accessToken }), - jumpToId, // Pass jumpToId as query param as fragmentIdentifier is not passed to server. - }, - fragmentIdentifier: jumpToId, // this is used by browser to scroll to correct block. - }); -}; - -export default { - getIFrameUrl, -}; diff --git a/src/courseware/course/sequence/Unit/urls.test.js b/src/courseware/course/sequence/Unit/urls.test.js deleted file mode 100644 index 9540f19e..00000000 --- a/src/courseware/course/sequence/Unit/urls.test.js +++ /dev/null @@ -1,83 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { stringifyUrl } from 'query-string'; -import { getIFrameUrl, iframeParams } from './urls'; - -jest.mock('@edx/frontend-platform', () => ({ - getConfig: jest.fn(), -})); -jest.mock('query-string', () => ({ - stringifyUrl: jest.fn((arg) => ({ stringifyUrl: arg })), -})); - -const config = { LMS_BASE_URL: 'test-lms-url' }; -getConfig.mockReturnValue(config); - -const props = { - id: 'test-id', - view: 'test-view', - format: 'test-format', - examAccess: { blockAccess: false, accessToken: 'test-access-token' }, - preview: false, -}; - -describe('urls module getIFrameUrl', () => { - test('format provided, exam access and token available', () => { - const url = stringifyUrl({ - url: `${config.LMS_BASE_URL}/xblock/${props.id}`, - query: { - ...iframeParams, - view: props.view, - format: props.format, - exam_access: props.examAccess.accessToken, - preview: props.preview, - }, - }); - expect(getIFrameUrl(props)).toEqual(url); - }); - test('no format provided, exam access blocked', () => { - const url = stringifyUrl({ - url: `${config.LMS_BASE_URL}/xblock/${props.id}`, - query: { ...iframeParams, view: props.view, preview: props.preview }, - }); - expect(getIFrameUrl({ - id: props.id, - view: props.view, - preview: props.preview, - examAccess: { blockAccess: true }, - })).toEqual(url); - }); - test('jumpToId and fragmentIdentifier is added to url', () => { - const url = stringifyUrl({ - url: `${config.LMS_BASE_URL}/xblock/${props.id}`, - query: { - ...iframeParams, - view: props.view, - format: props.format, - preview: props.preview, - exam_access: props.examAccess.accessToken, - jumpToId: 'some-xblock-id', - }, - fragmentIdentifier: 'some-xblock-id', - }); - expect(getIFrameUrl({ - ...props, - jumpToId: 'some-xblock-id', - })).toEqual(url); - }); - test('preview is true and url param equals 1', () => { - const url = stringifyUrl({ - url: `${config.LMS_BASE_URL}/xblock/${props.id}`, - query: { - ...iframeParams, - view: props.view, - format: props.format, - preview: true, - exam_access: props.examAccess.accessToken, - }, - }); - expect(getIFrameUrl({ - ...props, - preview: true, - })).toEqual(url); - }); -}); diff --git a/src/courseware/course/sequence/Unit/urls.test.ts b/src/courseware/course/sequence/Unit/urls.test.ts new file mode 100644 index 00000000..ea37f156 --- /dev/null +++ b/src/courseware/course/sequence/Unit/urls.test.ts @@ -0,0 +1,42 @@ +import { getConfig } from '@edx/frontend-platform'; +import { getIFrameUrl } from './urls'; + +jest.mock('@edx/frontend-platform', () => ({ + getConfig: jest.fn(), +})); +const config = { LMS_BASE_URL: 'https://test-lms-url' }; +getConfig.mockReturnValue(config); + +const props = { + id: 'test-id', + view: 'test-view', + format: 'test-format', + examAccess: { blockAccess: false, accessToken: 'test-access-token' }, + preview: false, +}; + +describe('urls module getIFrameUrl', () => { + test('format provided, exam access and token available', () => { + expect(getIFrameUrl(props)).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view'); + }); + test('no format provided, exam access blocked', () => { + expect(getIFrameUrl({ + id: props.id, + view: props.view, + preview: props.preview, + examAccess: { blockAccess: true }, + })).toEqual('https://test-lms-url/xblock/test-id?preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view'); + }); + test('jumpToId and fragmentIdentifier is added to url', () => { + expect(getIFrameUrl({ + ...props, + jumpToId: 'some-xblock-id', + })).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&jumpToId=some-xblock-id&preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view#some-xblock-id'); + }); + test('preview is true and url param equals 1', () => { + expect(getIFrameUrl({ + ...props, + preview: true, + })).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&preview=true&recheck_access=1&show_bookmark=0&show_title=0&view=test-view'); + }); +}); diff --git a/src/courseware/course/sequence/Unit/urls.ts b/src/courseware/course/sequence/Unit/urls.ts new file mode 100644 index 00000000..713aa0ec --- /dev/null +++ b/src/courseware/course/sequence/Unit/urls.ts @@ -0,0 +1,49 @@ +import { getConfig } from '@edx/frontend-platform'; + +export const iframeParams = { + show_title: 0, + show_bookmark: 0, + recheck_access: 1, +}; + +interface Props { + id: string; + view: string; + format?: string | null; + examAccess: { blockAccess: boolean, accessToken?: string }; + jumpToId?: string; + preview: boolean; +} + +export const getIFrameUrl = ({ + id, + view, + format = null, + examAccess, + jumpToId, + preview, +}: Props) => { + const xblockUrl = new URL(`${getConfig().LMS_BASE_URL}/xblock/${id}`); + for (const [key, value] of Object.entries(iframeParams)) { + xblockUrl.searchParams.set(key, String(value)); + } + xblockUrl.searchParams.set('view', view); + xblockUrl.searchParams.set('preview', String(preview)); + if (format) { + xblockUrl.searchParams.set('format', format); + } + if (!examAccess.blockAccess) { + xblockUrl.searchParams.set('exam_access', examAccess.accessToken!); + } + // Pass jumpToId as query param as fragmentIdentifier is not passed to server. + if (jumpToId) { + xblockUrl.searchParams.set('jumpToId', jumpToId); + xblockUrl.hash = `#${jumpToId}`; // this is used by browser to scroll to correct block. + } + xblockUrl.searchParams.sort(); + return xblockUrl.toString(); +}; + +export default { + getIFrameUrl, +}; diff --git a/src/courseware/course/share/ShareButton.jsx b/src/courseware/course/share/ShareButton.jsx deleted file mode 100644 index e4bf1a58..00000000 --- a/src/courseware/course/share/ShareButton.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { PropTypes } from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { TwitterShareButton, TwitterIcon } from 'react-share'; -import { stringifyUrl } from 'query-string'; - -import { Icon } from '@openedx/paragon'; -import messages from './messages'; - -const ShareTwitterIcon = () => ( - -); - -const ShareButton = ({ url }) => { - const { formatMessage } = useIntl(); - - const twitterUrl = stringifyUrl({ - url, - query: { - utm_source: 'twitter', - utm_medium: 'social', - utm_campaign: 'social-share-exp', - }, - }); - - return ( - - - {formatMessage(messages.shareButton)} - - ); -}; - -ShareButton.propTypes = { - url: PropTypes.string.isRequired, -}; - -export default ShareButton; diff --git a/src/courseware/course/share/messages.ts b/src/courseware/course/share/messages.ts deleted file mode 100644 index 9c9b0855..00000000 --- a/src/courseware/course/share/messages.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - shareButton: { - id: 'learn.sequence.share.button', - defaultMessage: 'Share this content', - description: 'share message button message', - }, - shareModalTitle: { - id: 'learn.sequence.share.modal.title', - defaultMessage: 'Title', - description: 'share message modal title', - }, - shareModalBody: { - id: 'learn.sequence.share.modal.body', - defaultMessage: 'Copy the link below to share this content.', - description: 'share message modal body', - }, - shareQuote: { - id: 'learn.sequence.share.quote', - defaultMessage: 'Here\'s a fun clip from a class I\'m taking on @edXonline.\n', - description: 'share message quote', - }, -}); - -export default messages;