Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
89032f0
chore(deps): update dependency @openedx/paragon to v23.14.8 (#732)
renovate[bot] Oct 13, 2025
f903392
fix(deps): update dependency core-js to v3.46.0 (#733)
renovate[bot] Oct 13, 2025
3b0c58f
fix: Run `npm audit fix` to update dependencies.
feanil Oct 15, 2025
745210b
feat: add program dashboard directory (#1)
MaxFrank13 Oct 15, 2025
91b6925
feat: add program dashboard directory (#1)
MaxFrank13 Oct 15, 2025
fa3f7b2
fix: Run `npm audit fix` to update dependencies. (#734)
feanil Oct 17, 2025
17ae415
feat: add program list page
MaxFrank13 Oct 17, 2025
b527fbc
chore(deps): update dependency @openedx/paragon to v23.14.9 (#735)
renovate[bot] Oct 20, 2025
aaf2e36
chore(deps): update dependency @reduxjs/toolkit to v2.9.1 (#736)
renovate[bot] Oct 20, 2025
51c4db5
feat: add program list view
MaxFrank13 Oct 21, 2025
5f0999f
fix: deps
MaxFrank13 Oct 21, 2025
6b99660
fix: tests
MaxFrank13 Oct 21, 2025
0d9be86
Merge branch 'program-dashboard-feature' into mfrank/add-program-list…
MaxFrank13 Oct 21, 2025
2b77225
fix: removed comment
MaxFrank13 Oct 21, 2025
0db621b
chore(deps): bump actions/setup-node from 5 to 6 (#737)
dependabot[bot] Oct 23, 2025
2221655
chore(deps): update dependency @edx/frontend-component-footer to v14.…
renovate[bot] Nov 3, 2025
b6ba8fb
chore(deps): update dependency @edx/frontend-platform to v8.5.2 (#740)
renovate[bot] Nov 3, 2025
e70fa29
chore(deps): update dependency @reduxjs/toolkit to v2.9.2 (#741)
renovate[bot] Nov 3, 2025
0a4285a
chore(deps): update dependency @openedx/paragon to v23.16.0 (#742)
renovate[bot] Nov 3, 2025
b926e13
chore(deps): update dependency @reduxjs/toolkit to v2.10.1 (#743)
renovate[bot] Nov 10, 2025
c38e805
fix(deps): update dependency @edx/frontend-component-header to v8 (#744)
renovate[bot] Nov 12, 2025
86b6574
[DEPR] feat!: remove notices wrapper (#731)
MaxFrank13 Nov 13, 2025
f59b501
fix(deps): update dependency react-router-dom to v6.30.2 (#745)
renovate[bot] Nov 17, 2025
85a5a6e
chore(deps): update dependency @openedx/paragon to v23.17.0 (#746)
renovate[bot] Nov 17, 2025
1a901f5
feat: add program list page
MaxFrank13 Nov 18, 2025
1f0b758
chore(deps): update dependency @openedx/paragon to v23.18.0 (#748)
renovate[bot] Nov 24, 2025
2a0ed57
chore(deps): update dependency @reduxjs/toolkit to v2.11.0 (#749)
renovate[bot] Nov 24, 2025
de68d2d
feat: program dashboard
MaxFrank13 Nov 25, 2025
6f6d16b
Merge branch 'master' into mfrank/add-program-list-page
MaxFrank13 Nov 25, 2025
3b83401
fix: test coverage
MaxFrank13 Nov 26, 2025
fd3ff70
fix: code cov
MaxFrank13 Nov 26, 2025
c277150
feat: added the ability for instances to use local translations fro…
jajjibhai008 Dec 3, 2025
f14ab88
chore(deps): update dependency @openedx/paragon to v23.18.1 (#755)
renovate[bot] Dec 8, 2025
324cb52
fix(deps): update dependency core-js to v3.47.0 (#757)
renovate[bot] Dec 8, 2025
a3e2c80
chore(deps): update dependency @reduxjs/toolkit to v2.11.1 (#756)
renovate[bot] Dec 8, 2025
19ccb8a
chore(deps): update dependency @reduxjs/toolkit to v2.11.2 (#761)
renovate[bot] Dec 15, 2025
62099a5
fix: env variables fetching issue for translations (#766)
jajjibhai008 Dec 17, 2025
75396f1
fix(deps): remove filesize dependency (#767)
MaxFrank13 Dec 18, 2025
22a1c65
chore(deps): bump actions/checkout from 5 to 6 (#750)
dependabot[bot] Dec 18, 2025
0a50937
chore(deps): update dependency @openedx/paragon to v23.18.2 (#771)
renovate[bot] Dec 22, 2025
247794b
fix(deps): update dependency react-router-dom to v6.30.3 (#780)
renovate[bot] Jan 12, 2026
9e61ae6
chore(deps): update dependency @openedx/paragon to v23.19.1 (#781)
renovate[bot] Jan 12, 2026
50ea19d
chore(deps): update dependency lodash to v4.17.23 [security] (#783)
renovate[bot] Jan 22, 2026
cfc8297
chore(deps): update dependency @edx/frontend-platform to v8.5.4 (#784)
renovate[bot] Jan 26, 2026
b21e6e5
fix: include frontend component header translation (#793)
DeimerM Feb 10, 2026
8fc839d
fix: remove unused universal-cookie dep (#794)
MaxFrank13 Feb 11, 2026
eef5d3f
fix: update react-share to v5 (#795)
MaxFrank13 Feb 12, 2026
5d10005
fix(deps): regenerate `package-lock.json` (#788)
brian-smith-tcril Feb 12, 2026
0bb1231
fix: requested changes
MaxFrank13 Feb 13, 2026
35680b8
fix(deps): update dependency core-js to v3.48.0 (#799)
renovate[bot] Feb 16, 2026
f93598a
chore(deps): update dependency @edx/frontend-platform to v8.5.5 (#798)
renovate[bot] Feb 16, 2026
ce0942d
feat: added the ability for instances to use local translations fro…
jajjibhai008 Dec 3, 2025
af9924e
chore(deps): update dependency @openedx/paragon to v23.18.1 (#755)
renovate[bot] Dec 8, 2025
cd37458
fix(deps): update dependency core-js to v3.47.0 (#757)
renovate[bot] Dec 8, 2025
68dcf1a
chore(deps): update dependency @reduxjs/toolkit to v2.11.1 (#756)
renovate[bot] Dec 8, 2025
3829dd7
chore(deps): update dependency @reduxjs/toolkit to v2.11.2 (#761)
renovate[bot] Dec 15, 2025
1bd4485
fix: env variables fetching issue for translations (#766)
jajjibhai008 Dec 17, 2025
5e4e347
fix(deps): remove filesize dependency (#767)
MaxFrank13 Dec 18, 2025
9f542d0
chore(deps): bump actions/checkout from 5 to 6 (#750)
dependabot[bot] Dec 18, 2025
fd7d4e4
chore(deps): update dependency @openedx/paragon to v23.18.2 (#771)
renovate[bot] Dec 22, 2025
c3a7c6d
fix(deps): update dependency react-router-dom to v6.30.3 (#780)
renovate[bot] Jan 12, 2026
b4adf16
chore(deps): update dependency @openedx/paragon to v23.19.1 (#781)
renovate[bot] Jan 12, 2026
9982ac4
chore(deps): update dependency lodash to v4.17.23 [security] (#783)
renovate[bot] Jan 22, 2026
23f23a4
chore(deps): update dependency @edx/frontend-platform to v8.5.4 (#784)
renovate[bot] Jan 26, 2026
26ce874
fix: include frontend component header translation (#793)
DeimerM Feb 10, 2026
1927792
fix: remove unused universal-cookie dep (#794)
MaxFrank13 Feb 11, 2026
17e316d
fix: update react-share to v5 (#795)
MaxFrank13 Feb 12, 2026
0d8c0ee
fix(deps): regenerate `package-lock.json` (#788)
brian-smith-tcril Feb 12, 2026
ff000f1
fix(deps): update dependency core-js to v3.48.0 (#799)
renovate[bot] Feb 16, 2026
5478431
chore(deps): update dependency @edx/frontend-platform to v8.5.5 (#798)
renovate[bot] Feb 16, 2026
d99e44a
feat: add program dashboard directory (#1)
MaxFrank13 Oct 15, 2025
19448cb
feat: add program list page
MaxFrank13 Oct 17, 2025
14406a8
fix: deps
MaxFrank13 Oct 21, 2025
489742d
feat: add program dashboard directory (#1)
MaxFrank13 Oct 15, 2025
f42207b
fix: requested changes
MaxFrank13 Feb 25, 2026
ae08970
fix: requested changes
MaxFrank13 Feb 25, 2026
bcd0c33
Merge branch 'master' into mfrank/add-program-list-page
MaxFrank13 Feb 25, 2026
f1180bf
fix(docs): use correct image for custom course banner (#796)
brian-smith-tcril Mar 2, 2026
0d2eb96
React query and react context conversion (#786)
jacobo-dominguez-wgu Mar 3, 2026
ca954e1
feat: showing course unenroll survey is configurable now (#738)
marslanabdulrauf Mar 6, 2026
a580d24
chore(deps): update dependency @tanstack/react-query to v5.90.21 (#803)
renovate[bot] Mar 9, 2026
305960a
chore(deps): update dependency react-share to v5.3.0 (#807)
renovate[bot] Mar 16, 2026
4bbf265
chore(deps): update dependency @tanstack/react-query to v5.95.0 (#808)
renovate[bot] Mar 23, 2026
09e0260
fix(deps): update dependency core-js to v3.49.0 (#809)
renovate[bot] Mar 23, 2026
5e69cf8
fix: baseAppUrl wrapping links (#813)
MaxFrank13 Mar 27, 2026
d343c89
chore(deps): update dependency @openedx/frontend-build to v14.6.3 (#814)
renovate[bot] Mar 30, 2026
2d361db
chore(deps): update dependency @openedx/paragon to v23.19.2 (#815)
renovate[bot] Mar 30, 2026
481f610
chore(deps): update dependency lodash to v4.18.1 (#821)
renovate[bot] Apr 6, 2026
1b6515c
chore(deps): update dependency @tanstack/react-query to v5.96.2 (#820)
renovate[bot] Apr 6, 2026
b96c3ef
Merge branch 'master' into mfrank/add-program-list-page
asharma12-sonata Apr 7, 2026
f9346d3
feat: refactor programs dashboard to use react querygit push
asharma12-sonata Apr 9, 2026
cd8e283
Resolved merge conflicts
asharma12-sonata Apr 9, 2026
6d961e6
feat: resolved merge conflicts
asharma12-sonata Apr 9, 2026
d7c195f
feat:removed unused code and uncommented the required piece of code
asharma12-sonata Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,287 changes: 2,232 additions & 55 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"access": "public"
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^8.0.0",
"@edx/frontend-enterprise-hotjar": "7.2.0",
Expand Down
36 changes: 10 additions & 26 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { Helmet } from 'react-helmet';

import { useIntl } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
Expand All @@ -11,9 +10,6 @@ import { Alert } from '@openedx/paragon';

import Dashboard from 'containers/Dashboard';

import AppWrapper from 'containers/AppWrapper';
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';

import { getConfig } from '@edx/frontend-platform';
import { useInitializeLearnerHome } from 'data/hooks';
import { useMasquerade } from 'data/context';
Expand Down Expand Up @@ -42,28 +38,16 @@ export const App = () => {
}
}, []);
return (
<>
<Helmet>
<title>{formatMessage(messages.pageTitle)}</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main id="main">
{hasNetworkFailure
? (
<Alert variant="danger">
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
</Alert>
) : (
<Dashboard />
)}
</main>
</AppWrapper>
<FooterSlot />
</div>
</>
<main id="main">
{hasNetworkFailure
? (
<Alert variant="danger">
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
</Alert>
) : (
<Dashboard />
)}
</main>
);
};

Expand Down
27 changes: 0 additions & 27 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ jest.mock('data/context', () => ({
useMasquerade: jest.fn(() => ({ masqueradeUser: null })),
}));

jest.mock('@edx/frontend-component-footer', () => ({
FooterSlot: jest.fn(() => <div>FooterSlot</div>),
}));
jest.mock('containers/Dashboard', () => jest.fn(() => <div>Dashboard</div>));
jest.mock('containers/LearnerDashboardHeader', () => jest.fn(() => <div>LearnerDashboardHeader</div>));
jest.mock('containers/AppWrapper', () => jest.fn(({ children }) => <div className="AppWrapper">{children}</div>));

jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({})),
Expand All @@ -43,31 +39,12 @@ useInitializeLearnerHome.mockReturnValue({

describe('App router component', () => {
describe('component', () => {
const runBasicTests = () => {
it('displays title in helmet component', async () => {
await waitFor(() => expect(document.title).toEqual(messages.pageTitle.defaultMessage));
});
it('displays learner dashboard header', () => {
const learnerDashboardHeader = screen.getByText('LearnerDashboardHeader');
expect(learnerDashboardHeader).toBeInTheDocument();
});
it('wraps the header and main components in an AppWrapper widget container', () => {
const appWrapper = screen.getByText('LearnerDashboardHeader').parentElement;
expect(appWrapper).toHaveClass('AppWrapper');
expect(appWrapper.children[1].id).toEqual('main');
});
it('displays footer slot', () => {
const footerSlot = screen.getByText('FooterSlot');
expect(footerSlot).toBeInTheDocument();
});
};
describe('no network failure', () => {
beforeEach(() => {
jest.clearAllMocks();
getConfig.mockReturnValue({});
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
runBasicTests();
it('loads dashboard', () => {
const dashboard = screen.getByText('Dashboard');
expect(dashboard).toBeInTheDocument();
Expand All @@ -79,7 +56,6 @@ describe('App router component', () => {
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
runBasicTests();
it('loads dashboard', () => {
const dashboard = screen.getByText('Dashboard');
expect(dashboard).toBeInTheDocument();
Expand All @@ -91,7 +67,6 @@ describe('App router component', () => {
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
runBasicTests();
it('loads dashboard', () => {
const dashboard = screen.getByText('Dashboard');
expect(dashboard).toBeInTheDocument();
Expand All @@ -107,7 +82,6 @@ describe('App router component', () => {
getConfig.mockReturnValue({});
render(<IntlProvider locale="en" messages={messages}><App /></IntlProvider>);
});
runBasicTests();
it('loads error page', () => {
const alert = screen.getByRole('alert');
expect(alert).toBeInTheDocument();
Expand All @@ -120,7 +94,6 @@ describe('App router component', () => {
getConfig.mockReturnValue({});
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
runBasicTests();
it('loads error page', () => {
const alert = screen.getByRole('alert');
expect(alert).toBeInTheDocument();
Expand Down
13 changes: 0 additions & 13 deletions src/containers/AppWrapper/index.jsx

This file was deleted.

15 changes: 0 additions & 15 deletions src/containers/AppWrapper/index.test.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions src/containers/Dashboard/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ jest.mock('./LoadingView', () => jest.fn(() => <div>LoadingView</div>));
jest.mock('containers/SelectSessionModal', () => jest.fn(() => <div>SelectSessionModal</div>));
jest.mock('./DashboardLayout', () => jest.fn(() => <div>DashboardLayout</div>));

const pageTitle = 'test-page-title';

describe('Dashboard', () => {
const createWrapper = (props = {}) => {
const {
Expand All @@ -42,11 +40,6 @@ describe('Dashboard', () => {
};

describe('render', () => {
it('page title is displayed in sr-only h1 tag', () => {
createWrapper();
const heading = screen.getByText(pageTitle);
expect(heading).toHaveClass('sr-only');
});
describe('initIsPending false', () => {
it('should render DashboardModalSlot', () => {
createWrapper({ initIsPending: false });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ const getLearnerHeaderMenu = (
courseSearchUrl,
authenticatedUser,
exploreCoursesClick,
pathname,
) => ({
mainMenu: [
{
type: 'item',
href: '/',
content: formatMessage(messages.course),
isActive: true,
isActive: pathname === '/',
},
...(getConfig().ENABLE_PROGRAMS ? [{
type: 'item',
href: `${urls.programsUrl()}`,
href: getConfig().ENABLE_PROGRAM_DASHBOARD ? '/programs' : `${urls.programsUrl()}`,
content: formatMessage(messages.program),
isActive: pathname === '/programs',
}] : []),
...(!getConfig().NON_BROWSABLE_COURSES ? [{
type: 'item',
Expand Down
4 changes: 2 additions & 2 deletions src/containers/LearnerDashboardHeader/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export const findCoursesNavClicked = (href) => track.findCourses.findCoursesClic
});

export const useLearnerDashboardHeaderMenu = ({
courseSearchUrl, authenticatedUser, exploreCoursesClick,
courseSearchUrl, authenticatedUser, exploreCoursesClick, pathname,
}) => {
const { formatMessage } = useIntl();
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick);
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick, pathname);
};

export default {
Expand Down
18 changes: 16 additions & 2 deletions src/containers/LearnerDashboardHeader/index.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import React from 'react';
import { Helmet } from 'react-helmet';

import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import MasqueradeBar from 'containers/MasqueradeBar';
import { AppContext } from '@edx/frontend-platform/react';
import Header from '@edx/frontend-component-header';
import { useInitializeLearnerHome } from 'data/hooks';
import urls from 'data/services/lms/urls';

import { useLocation } from 'react-router-dom';
import { useDashboardMessages } from 'containers/Dashboard/hooks';
import ConfirmEmailBanner from './ConfirmEmailBanner';

import appMessages from '../../messages';
import { useLearnerDashboardHeaderMenu, findCoursesNavClicked } from './hooks';

import './index.scss';

export const LearnerDashboardHeader = () => {
const { authenticatedUser } = React.useContext(AppContext);
const { formatMessage } = useIntl();
const { pageTitle } = useDashboardMessages();
const location = useLocation();
const { pathname } = location;
const { data: learnerData } = useInitializeLearnerHome();
const courseSearchUrl = learnerData?.platformSettings?.courseSearchUrl || '';

Expand All @@ -25,16 +33,22 @@ export const LearnerDashboardHeader = () => {
courseSearchUrl,
authenticatedUser,
exploreCoursesClick,
pathname,
});

return (
<>
<Helmet>
<title>{formatMessage(appMessages.pageTitle)}</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<ConfirmEmailBanner />
<Header
mainMenuItems={learnerHomeHeaderMenu.mainMenu}
secondaryMenuItems={learnerHomeHeaderMenu.secondaryMenu}
userMenuItems={learnerHomeHeaderMenu.userMenu}
/>
<h1 className="sr-only">{pageTitle}</h1>
<MasqueradeBar />
</>
);
Expand Down
40 changes: 40 additions & 0 deletions src/containers/LearnerDashboardHeader/index.test.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { mergeConfig } from '@edx/frontend-platform';
import { render, screen } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useLocation } from 'react-router-dom';

import urls from 'data/services/lms/urls';
import { useDashboardMessages } from 'containers/Dashboard/hooks';
import LearnerDashboardHeader from '.';
import { findCoursesNavClicked } from './hooks';

Expand All @@ -22,16 +24,34 @@ jest.mock('./hooks', () => ({
findCoursesNavClicked: jest.fn(),
}));

jest.mock('react-router-dom', () => ({
useLocation: jest.fn(() => ({
pathname: '/',
})),
}));

const mockedHeaderProps = jest.fn();
jest.mock('containers/MasqueradeBar', () => jest.fn(() => <div>MasqueradeBar</div>));
jest.mock('./ConfirmEmailBanner', () => jest.fn(() => <div>ConfirmEmailBanner</div>));
jest.mock('@edx/frontend-component-header', () => jest.fn((props) => {
mockedHeaderProps(props);
return <div>Header</div>;
}));
jest.mock('containers/Dashboard/hooks', () => ({
useDashboardMessages: jest.fn(),
}));

const pageTitle = 'test-page-title';

describe('LearnerDashboardHeader', () => {
beforeEach(() => jest.clearAllMocks());

it('page title is displayed in sr-only h1 tag', () => {
useDashboardMessages.mockReturnValue({ pageTitle });
render(<IntlProvider locale="en"><LearnerDashboardHeader /></IntlProvider>);
const heading = screen.getByText(pageTitle);
expect(heading).toHaveClass('sr-only');
});
it('renders and discover url is correct', () => {
mergeConfig({ ORDER_HISTORY_URL: 'test-url' });
render(<IntlProvider locale="en"><LearnerDashboardHeader /></IntlProvider>);
Expand Down Expand Up @@ -60,6 +80,26 @@ describe('LearnerDashboardHeader', () => {
const { mainMenuItems } = props;
expect(mainMenuItems.length).toBe(3);
});

it('should highlight the active tab depending on the pathname', () => {
render(<IntlProvider locale="en"><LearnerDashboardHeader /></IntlProvider>);
const props = mockedHeaderProps.mock.calls[0][0];
const { mainMenuItems } = props;
expect(mainMenuItems[0].isActive).toBe(true);
});

it('should highlight the programs tab if dashboard is enabled and on the programs page', () => {
mergeConfig({ ENABLE_PROGRAMS: true, ENABLE_PROGRAM_DASHBOARD: true });
useLocation.mockReturnValueOnce({
pathname: '/programs',
});
render(<IntlProvider locale="en"><LearnerDashboardHeader /></IntlProvider>);
const props = mockedHeaderProps.mock.calls[0][0];
const { mainMenuItems } = props;
expect(mainMenuItems[0].isActive).toBe(false);
expect(mainMenuItems[1].isActive).toBe(true);
});

it('should not display Discover New tab if it is disabled by configuration', () => {
mergeConfig({ NON_BROWSABLE_COURSES: true });
render(<IntlProvider locale="en"><LearnerDashboardHeader /></IntlProvider>);
Expand Down
Loading
Loading