Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
741c9ce
chore: updating the workflows
deborahgu 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
17ae415
feat: add program list page
MaxFrank13 Oct 17, 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
1a901f5
feat: add program list page
MaxFrank13 Nov 18, 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
0bb1231
fix: requested changes
MaxFrank13 Feb 13, 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
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
3ebc468
chore: Merge pull request #2 from edx/dkaplan1/modify-workflow-branches
deborahgu Oct 15, 2025
83d8908
feat: sync master branch to openedx master (#10)
MaxFrank13 Feb 24, 2026
44adf82
feat!: sync react query conversion (#11)
MaxFrank13 Apr 6, 2026
9991a59
Resolved merge conflicts
asharma12-sonata Apr 9, 2026
2aee044
feat: resolved merge conflicts
asharma12-sonata Apr 9, 2026
a840d25
feat: removed unused code and uncommented the required piece of code
asharma12-sonata Apr 9, 2026
a9a374d
feat: reinstalled npm packages to update package.lock.json to resolve…
asharma12-sonata Apr 10, 2026
f0050e0
feat: reverted github workflow changes
asharma12-sonata Apr 10, 2026
2fa2554
feat: fixed eslint reported issues
asharma12-sonata Apr 10, 2026
1e28881
feat: fixed workflow for openedx
asharma12-sonata Apr 10, 2026
4e986db
feat: added footer as per jason comment in PR review
asharma12-sonata Apr 13, 2026
8f594fb
feat: fixed lint issue
asharma12-sonata Apr 13, 2026
b008d21
feat: handled test case failures
asharma12-sonata Apr 14, 2026
7b11273
feat: updated test cases as per Max's comment
asharma12-sonata Apr 17, 2026
e1eb382
feat: changes done as per the review comment
asharma12-sonata Apr 21, 2026
8ca8f0d
feat: updates made as per the PR review comments also updated the key…
asharma12-sonata Apr 22, 2026
79723a8
Merge branch 'master' into abhishek/refactor-program-dashboard
asharma12-sonata Apr 22, 2026
4e3b242
Merge branch 'master' into abhishek/refactor-program-dashboard
asharma12-sonata Apr 23, 2026
2dd9c30
feat: updated package.lock.json as it was causing issue in ci
asharma12-sonata Apr 23, 2026
04b5928
fix: fixed test cases failure issue
asharma12-sonata Apr 23, 2026
3a903d8
fix: updates as per the review comments
asharma12-sonata Apr 29, 2026
de74064
fix: updates made in type case as per the pr review comment
asharma12-sonata May 7, 2026
a4fa71d
Merge branch 'master' into abhishek/refactor-program-dashboard
asharma12-sonata May 7, 2026
5bec471
fix: fixed test cases and uncommented the check
asharma12-sonata May 8, 2026
5cc08bb
fix: fixed Ci issue
asharma12-sonata May 8, 2026
bacafec
fix: fixes done as per the review comments
asharma12-sonata May 11, 2026
61e8929
fix: moved masquerade bar to dashboard
asharma12-sonata May 18, 2026
fcda787
fix: updated test cases after moving masquerade bar
asharma12-sonata May 18, 2026
b7733e7
Merge branch 'master' of github.com:openedx/frontend-app-learner-dash…
asharma12-sonata May 18, 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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ NON_BROWSABLE_COURSES=false
SHOW_UNENROLL_SURVEY=true
# Fallback in local style files
PARAGON_THEME_URLS={}
ENABLE_PROGRAM_DASHBOARD=false
16 changes: 9 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 10 additions & 28 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import React from 'react';
import { Helmet } from 'react-helmet';

import { useIntl } from '@edx/frontend-platform/i18n';

import { ErrorPage } from '@edx/frontend-platform/react';
import { FooterSlot } from '@edx/frontend-component-footer';
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';
import messages from './messages';
Expand All @@ -26,28 +20,16 @@ export const App = () => {
const supportEmail = data?.platformSettings?.supportEmail || undefined;

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
37 changes: 1 addition & 36 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
Expand All @@ -7,20 +7,8 @@ import { useInitializeLearnerHome } from 'data/hooks';
import { App } from './App';
import messages from './messages';

jest.mock('data/hooks', () => ({
useInitializeLearnerHome: jest.fn(),
}));

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 +31,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 +48,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 +59,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 +74,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 +86,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
1 change: 1 addition & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const configuration = {
ENABLE_PROGRAMS: process.env.ENABLE_PROGRAMS === 'true',
NON_BROWSABLE_COURSES: process.env.NON_BROWSABLE_COURSES === 'true',
SHOW_UNENROLL_SURVEY: process.env.SHOW_UNENROLL_SURVEY === 'true',
ENABLE_PROGRAM_DASHBOARD: process.env.ENABLE_PROGRAM_DASHBOARD === 'true',
};

const features = {};
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.

38 changes: 21 additions & 17 deletions src/containers/Dashboard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SelectSessionModal from 'containers/SelectSessionModal';
import CoursesPanel from 'containers/CoursesPanel';
import DashboardModalSlot from 'plugin-slots/DashboardModalSlot';

import MasqueradeBar from 'containers/MasqueradeBar';
import LoadingView from './LoadingView';
import DashboardLayout from './DashboardLayout';
import hooks from './hooks';
Expand All @@ -20,24 +21,27 @@ export const Dashboard = () => {
const hasCourses = useMemo(() => data?.courses?.length > 0, [data]);

return (
<div id="dashboard-container" className="d-flex flex-column p-2 pt-0">
<h1 className="sr-only">{pageTitle}</h1>
{!isPending && (
<>
<DashboardModalSlot />
{(hasCourses && showSelectSessionModal) && <SelectSessionModal />}
</>
)}
<div id="dashboard-content" data-testid="dashboard-content">
{isPending
? (<LoadingView />)
: (
<DashboardLayout>
<CoursesPanel />
</DashboardLayout>
)}
<>
<MasqueradeBar />
<div id="dashboard-container" className="d-flex flex-column p-2 pt-0">
<h1 className="sr-only">{pageTitle}</h1>
{!isPending && (
<>
<DashboardModalSlot />
{(hasCourses && showSelectSessionModal) && <SelectSessionModal />}
</>
)}
<div id="dashboard-content" data-testid="dashboard-content">
{isPending
? (<LoadingView />)
: (
<DashboardLayout>
<CoursesPanel />
</DashboardLayout>
)}
</div>
</div>
</div>
</>
);
};

Expand Down
20 changes: 12 additions & 8 deletions src/containers/Dashboard/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ jest.mock('containers/CoursesPanel', () => jest.fn(() => <div>CoursesPanel</div>
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';
jest.mock('containers/MasqueradeBar', () => jest.fn(() => <div>MasqueradeBar</div>)); // Mock the MasqueradeBar

describe('Dashboard', () => {
const createWrapper = (props = {}) => {
Expand All @@ -34,19 +33,14 @@ describe('Dashboard', () => {
initIsPending = true,
showSelectSessionModal = true,
} = props;
hooks.useDashboardMessages.mockReturnValue({ pageTitle });
hooks.useDashboardMessages.mockReturnValue({ pageTitle: 'Dashboard' });
const dataMocked = { data: hasCourses ? { courses: [1, 2] } : { courses: [] }, isPending: initIsPending };
useInitializeLearnerHome.mockReturnValue(dataMocked);
useSelectSessionModal.mockReturnValue({ selectSessionModal: showSelectSessionModal ? { cardId: 1 } : null });
return render(<IntlProvider locale="en"><Dashboard /></IntlProvider>);
};

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 All @@ -58,6 +52,11 @@ describe('Dashboard', () => {
const selectSessionModal = screen.getByText('SelectSessionModal');
expect(selectSessionModal).toBeInTheDocument();
});
it('should render MasqueradeBar', () => {
createWrapper({ initIsPending: false });
const masqueradeBar = screen.getByText('MasqueradeBar');
expect(masqueradeBar).toBeInTheDocument();
});
});
describe('courses still loading', () => {
it('should render LoadingView', () => {
Expand All @@ -72,6 +71,11 @@ describe('Dashboard', () => {
const dashboardLayout = screen.getByText('DashboardLayout');
expect(dashboardLayout).toBeInTheDocument();
});
it('should render MasqueradeBar', () => {
createWrapper({ initIsPending: false });
const masqueradeBar = screen.getByText('MasqueradeBar');
expect(masqueradeBar).toBeInTheDocument();
});
});
});
});
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
Loading
Loading