-
{classInfo?.className}
-
+
{date}
diff --git a/src/features/Instructors/InstructorDetailTable/columns.jsx b/src/features/Instructors/InstructorDetailTable/columns.jsx
index a02d6264..67b54cbf 100644
--- a/src/features/Instructors/InstructorDetailTable/columns.jsx
+++ b/src/features/Instructors/InstructorDetailTable/columns.jsx
@@ -1,22 +1,23 @@
/* eslint-disable react/prop-types, no-nested-ternary */
import React from 'react';
-import { Link } from 'react-router-dom';
import { Badge } from 'react-paragon-topaz';
import { ClassStatus, badgeVariants } from 'features/constants';
import { formatUTCDate } from 'helpers';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
+
const columns = [
{
Header: 'Class',
accessor: 'className',
Cell: ({ row }) => (
-
{row.values.className}
-
+
),
},
{
diff --git a/src/features/Instructors/InstructorsDetailPage/index.jsx b/src/features/Instructors/InstructorsDetailPage/index.jsx
index 4313f96c..1b3db6f5 100644
--- a/src/features/Instructors/InstructorsDetailPage/index.jsx
+++ b/src/features/Instructors/InstructorsDetailPage/index.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
-import { Link, useParams, useHistory } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import {
Container,
@@ -14,10 +14,15 @@ import { fetchInstructorsData } from 'features/Instructors/data/thunks';
import { columns } from 'features/Instructors/InstructorDetailTable/columns';
import { initialPage, RequestStatus } from 'features/constants';
+import { useInstitutionIdQueryParam } from 'hooks';
+
+import LinkWithQuery from 'features/Main/LinkWithQuery';
+
const InstructorsDetailPage = () => {
const history = useHistory();
const dispatch = useDispatch();
const { instructorUsername } = useParams();
+ const addQueryParam = useInstitutionIdQueryParam();
const institutionRef = useRef(undefined);
const [currentPage, setCurrentPage] = useState(initialPage);
@@ -59,17 +64,18 @@ const InstructorsDetailPage = () => {
}
if (institution.id !== institutionRef.current) {
- history.push('/instructors');
+ history.push(addQueryParam('/instructors'));
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [institution, history]);
return (
-
+
-
+
{instructorInfo.instructorName}
diff --git a/src/features/Instructors/InstructorsTable/columns.jsx b/src/features/Instructors/InstructorsTable/columns.jsx
index c27db08a..118a65b2 100644
--- a/src/features/Instructors/InstructorsTable/columns.jsx
+++ b/src/features/Instructors/InstructorsTable/columns.jsx
@@ -1,20 +1,21 @@
/* eslint-disable react/prop-types, no-nested-ternary */
import { differenceInHours, differenceInDays, differenceInWeeks } from 'date-fns';
-import { Link } from 'react-router-dom';
import { daysWeek, hoursDay } from 'features/constants';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
+
const columns = [
{
Header: 'Instructor',
accessor: 'instructorName',
Cell: ({ row }) => (
-
{row.values.instructorName}
-
+
),
},
{
diff --git a/src/features/Licenses/LicenseDetailTable/columns.jsx b/src/features/Licenses/LicenseDetailTable/columns.jsx
index d29e7a14..a81aa1a9 100644
--- a/src/features/Licenses/LicenseDetailTable/columns.jsx
+++ b/src/features/Licenses/LicenseDetailTable/columns.jsx
@@ -1,12 +1,12 @@
/* eslint-disable react/prop-types, no-nested-ternary */
-import { Link } from 'react-router-dom';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
const columns = [
{
Header: 'Course',
accessor: 'masterCourseName',
Cell: ({ row }) => (
- {row.values.masterCourseName}
+ {row.values.masterCourseName}
),
},
{
diff --git a/src/features/Licenses/LicensesDetailPage/index.jsx b/src/features/Licenses/LicensesDetailPage/index.jsx
index 4fa46056..9ff8b431 100644
--- a/src/features/Licenses/LicensesDetailPage/index.jsx
+++ b/src/features/Licenses/LicensesDetailPage/index.jsx
@@ -1,22 +1,27 @@
import React, { useState, useEffect, useRef } from 'react';
-import { Link, useParams, useHistory } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
-
import { Button } from 'react-paragon-topaz';
import { Container, Pagination, Spinner } from '@edx/paragon';
+
import Table from 'features/Main/Table';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
+
import { columns } from 'features/Licenses/LicenseDetailTable/columns';
import { fetchCoursesData } from 'features/Courses/data/thunks';
import { resetCoursesTable, updateCurrentPage } from 'features/Courses/data/slice';
import { fetchLicensesData } from 'features/Licenses/data';
import { initialPage, licenseBuyLink, RequestStatus } from 'features/constants';
+import { useInstitutionIdQueryParam } from 'hooks';
+
import 'features/Licenses/LicensesDetailPage/index.scss';
const LicensesDetailPage = () => {
const history = useHistory();
const dispatch = useDispatch();
const { licenseId } = useParams();
+ const addQueryParam = useInstitutionIdQueryParam();
const institutionRef = useRef(undefined);
const [currentPage, setCurrentPage] = useState(initialPage);
@@ -58,17 +63,18 @@ const LicensesDetailPage = () => {
}
if (institution.id !== institutionRef.current) {
- history.push('/licenses');
+ history.push(addQueryParam('/licenses'));
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [institution, history]);
return (
-
+
-
+
License pool: {licenseInfo.licenseName}
diff --git a/src/features/Licenses/LicensesTable/columns.jsx b/src/features/Licenses/LicensesTable/columns.jsx
index ec20cf12..6e3c2699 100644
--- a/src/features/Licenses/LicensesTable/columns.jsx
+++ b/src/features/Licenses/LicensesTable/columns.jsx
@@ -1,11 +1,11 @@
/* eslint-disable react/prop-types */
-import { Link } from 'react-router-dom';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
const columns = [
{
Header: 'License Pool',
accessor: 'licenseName',
- Cell: ({ row }) => ({row.values.licenseName}),
+ Cell: ({ row }) => ({row.values.licenseName}),
},
{
Header: 'Purchased',
diff --git a/src/features/Main/InstitutionSelector/__test__/index.test.jsx b/src/features/Main/InstitutionSelector/__test__/index.test.jsx
new file mode 100644
index 00000000..e8eb5955
--- /dev/null
+++ b/src/features/Main/InstitutionSelector/__test__/index.test.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { Router } from 'react-router-dom';
+import { fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+
+import InstitutionSelector from 'features/Main/InstitutionSelector';
+import { renderWithProviders } from 'test-utils';
+
+jest.mock('react-select', () => function reactSelect({ options, currentValue, onChange }) {
+ function handleChange(event) {
+ onChange({ id: event.currentTarget.value });
+
+ return event;
+ }
+
+ return (
+
+ );
+});
+
+describe('InstitutionSelector', () => {
+ let history;
+
+ beforeEach(() => {
+ history = {
+ location: { search: '' },
+ push: jest.fn(),
+ replace: jest.fn(),
+ listen: jest.fn(),
+ createHref: jest.fn(),
+ };
+ });
+
+ test('Should render the select options and handle selection', () => {
+ const preloadedState = {
+ main: {
+ institution: {
+ data: [
+ { id: 1, name: 'Institution 1' },
+ { id: 2, name: 'Institution 2' },
+ ],
+ },
+ selectedInstitution: null,
+ },
+ };
+
+ const { getByText, getByTestId } = renderWithProviders(
+
+
+ ,
+ { preloadedState },
+ );
+
+ expect(getByText('Select an institution')).toBeInTheDocument();
+
+ fireEvent.change(getByTestId('select'), { target: { value: '1', id: '1' } });
+
+ expect(getByText('Institution 1')).toBeInTheDocument();
+ });
+});
diff --git a/src/features/Main/InstitutionSelector/index.jsx b/src/features/Main/InstitutionSelector/index.jsx
new file mode 100644
index 00000000..0170a9ef
--- /dev/null
+++ b/src/features/Main/InstitutionSelector/index.jsx
@@ -0,0 +1,84 @@
+import React, {
+ useEffect,
+ useState,
+} from 'react';
+
+import { Row, Col } from '@edx/paragon';
+import { Select } from 'react-paragon-topaz';
+
+import { useLocation, useHistory } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
+
+import { formatSelectOptions } from 'helpers';
+import { INSTITUTION_QUERY_ID } from 'features/constants';
+
+import { updateSelectedInstitution } from 'features/Main/data/slice';
+
+const selectorStyles = {
+ control: base => ({
+ ...base,
+ padding: '3px',
+ }),
+};
+
+const InstitutionSelector = () => {
+ const dispatch = useDispatch();
+
+ const history = useHistory();
+ const location = useLocation();
+ const institutions = useSelector((state) => state.main.institution.data);
+ const selectedInstitution = useSelector((state) => state.main.selectedInstitution);
+
+ const [institutionOptions, setInstitutionOptions] = useState([]);
+
+ const searchParams = new URLSearchParams(location.search);
+
+ const updateQueryParam = (value) => {
+ const { id = '' } = value;
+ searchParams.set(INSTITUTION_QUERY_ID, id);
+ history.push({ search: searchParams.toString() });
+
+ dispatch(updateSelectedInstitution(value));
+ };
+
+ useEffect(() => {
+ if (institutions.length > 0) {
+ const institutionId = searchParams.get(INSTITUTION_QUERY_ID);
+
+ const options = formatSelectOptions(institutions);
+ setInstitutionOptions(options);
+
+ if (institutionId) {
+ const institutionByQuery = options.filter((option) => option.id === parseInt(institutionId, 10));
+
+ if (institutionByQuery[0]) {
+ dispatch(updateSelectedInstitution(institutionByQuery[0]));
+ } else {
+ dispatch(updateSelectedInstitution(options[0]));
+ }
+ } else {
+ updateQueryParam(options[0]);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [institutions, dispatch]);
+
+ return (
+
+
+ Select an institution
+
+
+
+ );
+};
+
+export default InstitutionSelector;
diff --git a/src/features/Main/LinkWithQuery/__test__/index.test.jsx b/src/features/Main/LinkWithQuery/__test__/index.test.jsx
new file mode 100644
index 00000000..1d5afee1
--- /dev/null
+++ b/src/features/Main/LinkWithQuery/__test__/index.test.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { BrowserRouter } from 'react-router-dom';
+import '@testing-library/jest-dom/extend-expect';
+
+import { renderWithProviders } from 'test-utils';
+import LinkWithQuery from 'features/Main/LinkWithQuery';
+
+import { INSTITUTION_QUERY_ID } from 'features/constants';
+
+describe('LinkWithQuery', () => {
+ test('Should render the link with the URL unchanged if institutionId is not defined', () => {
+ const preloadedState = {
+ main: {
+ selectedInstitution: null,
+ },
+ };
+
+ const { getByText } = renderWithProviders(
+
+ Test Link
+ ,
+ { preloadedState },
+ );
+
+ const linkElement = getByText('Test Link');
+ expect(linkElement).toHaveAttribute('href', '/courses');
+ });
+
+ test('Should render the link with the institutionId as a query param if institutionId is defined', () => {
+ const institutionId = '12345';
+ const preloadedState = {
+ main: {
+ selectedInstitution: { id: institutionId },
+ },
+ };
+
+ const { getByText } = renderWithProviders(
+
+ Test Link
+ ,
+ { preloadedState },
+ );
+
+ const linkElement = getByText('Test Link');
+ expect(linkElement).toHaveAttribute('href', `/courses?${INSTITUTION_QUERY_ID}=${institutionId}`);
+ });
+
+ test('Should render the link with the institutionId appended as a query param if other query params exist', () => {
+ const institutionId = '12345';
+ const preloadedState = {
+ main: {
+ selectedInstitution: { id: institutionId },
+ },
+ };
+
+ const { getByText } = renderWithProviders(
+
+ Test Link
+ ,
+ { preloadedState },
+ );
+
+ const linkElement = getByText('Test Link');
+ expect(linkElement).toHaveAttribute('href', `/courses?foo=bar&${INSTITUTION_QUERY_ID}=${institutionId}`);
+ });
+});
diff --git a/src/features/Main/LinkWithQuery/index.jsx b/src/features/Main/LinkWithQuery/index.jsx
new file mode 100644
index 00000000..5dd3bd4b
--- /dev/null
+++ b/src/features/Main/LinkWithQuery/index.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+
+import { useInstitutionIdQueryParam } from 'hooks';
+
+const LinkWithQuery = ({ children, ...props }) => {
+ const addQueryParam = useInstitutionIdQueryParam();
+ const link = addQueryParam(props.to);
+
+ return (
+
+ {children}
+
+ );
+};
+
+LinkWithQuery.propTypes = {
+ to: PropTypes.string.isRequired,
+ children: PropTypes.node.isRequired,
+};
+
+export default LinkWithQuery;
diff --git a/src/features/Main/Sidebar/index.jsx b/src/features/Main/Sidebar/index.jsx
index 60e87cd8..8ece2fc3 100644
--- a/src/features/Main/Sidebar/index.jsx
+++ b/src/features/Main/Sidebar/index.jsx
@@ -5,6 +5,9 @@ import { useHistory } from 'react-router-dom';
import { Sidebar as SidebarBase, MenuSection, MenuItem } from 'react-paragon-topaz';
import { updateActiveTab } from 'features/Main/data/slice';
+
+import { useInstitutionIdQueryParam } from 'hooks';
+
import './index.scss';
const menuItems = [
@@ -50,6 +53,8 @@ export const Sidebar = () => {
history.push(`/${tabName}`);
};
+ const addQueryParam = useInstitutionIdQueryParam();
+
return (
@@ -58,7 +63,7 @@ export const Sidebar = () => {