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;