Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
43f881d
fix(deps): remove filesize dependency (#767)
MaxFrank13 Dec 18, 2025
7d789a9
chore(deps): update dependency @openedx/paragon to v23.18.2 (#771)
renovate[bot] Dec 22, 2025
7aaad1b
fix: remove unused universal-cookie dep (#794)
MaxFrank13 Feb 11, 2026
773a6c6
fix: update react-share to v5 (#795)
MaxFrank13 Feb 12, 2026
07620df
fix(deps): regenerate `package-lock.json` (#788)
brian-smith-tcril Feb 12, 2026
4bede97
fix(deps): update dependency core-js to v3.48.0 (#799)
renovate[bot] Feb 16, 2026
8137a1b
chore(deps): update dependency @edx/frontend-platform to v8.5.5 (#798)
renovate[bot] Feb 16, 2026
50714ae
fix(docs): use correct image for custom course banner (#796)
brian-smith-tcril Mar 2, 2026
d6eaf3f
feat!: React query and react context conversion (#786)
jacobo-dominguez-wgu Mar 3, 2026
e463aad
feat: showing course unenroll survey is configurable now (#738)
marslanabdulrauf Mar 6, 2026
8540ac6
chore(deps): update dependency @tanstack/react-query to v5.90.21 (#803)
renovate[bot] Mar 9, 2026
8eb971c
chore(deps): update dependency react-share to v5.3.0 (#807)
renovate[bot] Mar 16, 2026
279e5e1
chore(deps): update dependency @tanstack/react-query to v5.95.0 (#808)
renovate[bot] Mar 23, 2026
cf1584a
fix(deps): update dependency core-js to v3.49.0 (#809)
renovate[bot] Mar 23, 2026
fcdb0b9
chore: sync fork from upstream master (#4)
MaxFrank13 Oct 27, 2025
ed8e5b7
fix: baseAppUrl add ins
MaxFrank13 Mar 25, 2026
f3953db
fix: package-lock
MaxFrank13 Mar 26, 2026
7cf7d6d
fix: test
MaxFrank13 Mar 27, 2026
8e91515
fix: lint
MaxFrank13 Mar 27, 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 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NODE_ENV='production'
APP_ID='learner-dashboard'
NODE_PATH=./src
BASE_URL=''
LMS_BASE_URL=''
Expand Down Expand Up @@ -41,5 +42,6 @@ CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false
SHOW_UNENROLL_SURVEY=true
# Fallback in local style files
PARAGON_THEME_URLS={}
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NODE_ENV='development'
APP_ID='learner-dashboard'
PORT=1996
BASE_URL='localhost:1996'
LMS_BASE_URL='http://localhost:18000'
Expand Down Expand Up @@ -47,5 +48,6 @@ CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false
SHOW_UNENROLL_SURVEY=true
# Fallback in local style files
PARAGON_THEME_URLS={}
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NODE_ENV='test'
APP_ID='learner-dashboard'
PORT=1996
BASE_URL='localhost:1996'
LMS_BASE_URL='http://localhost:18000'
Expand Down Expand Up @@ -46,4 +47,5 @@ CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=true
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false
SHOW_UNENROLL_SURVEY=true
PARAGON_THEME_URLS={}
2 changes: 2 additions & 0 deletions example.env.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ frontend-platform's getConfig loads configuration in the following sequence:

module.exports = {
NODE_ENV: 'development',
APP_ID: 'learner-dashboard',
NODE_PATH: './src',
PORT: 1996,
BASE_URL: 'localhost:1996',
Expand Down Expand Up @@ -69,4 +70,5 @@ module.exports = {
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
CAREER_LINK_URL: '',
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
SHOW_UNENROLL_SURVEY: true
};
3,782 changes: 2,073 additions & 1,709 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/paragon": "^23.4.5",
"@redux-devtools/extension": "3.3.0",
"@reduxjs/toolkit": "^2.0.0",
"@tanstack/react-query": "^5.90.16",
"classnames": "^2.3.1",
"core-js": "3.47.0",
"core-js": "3.49.0",
"font-awesome": "4.7.0",
"history": "5.3.0",
"lodash": "^4.17.21",
Expand All @@ -54,14 +53,9 @@
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-intl": "6.8.9",
"react-redux": "^7.2.4",
"react-router-dom": "6.30.3",
"react-share": "^4.4.0",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-thunk": "2.4.2",
"react-share": "^5.2.2",
"regenerator-runtime": "^0.14.0",
"reselect": "^4.0.0",
"util": "^0.12.4"
},
"devDependencies": {
Expand All @@ -78,7 +72,6 @@
"jest-expect-message": "^1.1.3",
"jest-when": "^3.6.0",
"react-dev-utils": "^12.0.0",
"react-test-renderer": "^18.3.1",
"redux-mock-store": "^1.5.4"
"react-test-renderer": "^18.3.1"
}
}
48 changes: 9 additions & 39 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,30 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';

import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
import { ErrorPage } from '@edx/frontend-platform/react';
import { FooterSlot } from '@edx/frontend-component-footer';
import { Alert } from '@openedx/paragon';

import { RequestKeys } from 'data/constants/requests';
import store from 'data/store';
import {
selectors,
actions,
} from 'data/redux';
import { reduxHooks } from 'hooks';
import Dashboard from 'containers/Dashboard';

import track from 'tracking';

import fakeData from 'data/services/lms/fakeData/courses';

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';
import './App.scss';

export const App = () => {
const { authenticatedUser } = React.useContext(AppContext);
const { formatMessage } = useIntl();
const isFailed = {
initialize: reduxHooks.useRequestIsFailed(RequestKeys.initialize),
refreshList: reduxHooks.useRequestIsFailed(RequestKeys.refreshList),
};
const hasNetworkFailure = isFailed.initialize || isFailed.refreshList;
const { supportEmail } = reduxHooks.usePlatformSettingsData();
const loadData = reduxHooks.useLoadData();
const { masqueradeUser } = useMasquerade();
const { data, isError } = useInitializeLearnerHome();
const hasNetworkFailure = !masqueradeUser && isError;
const supportEmail = data?.platformSettings?.supportEmail || undefined;

/* istanbul ignore next */
React.useEffect(() => {
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
window.loadEmptyData = () => {
loadData({ ...fakeData.globalData, courses: [] });
};
window.loadMockData = () => {
loadData({
...fakeData.globalData,
courses: [
...fakeData.courseRunData,
...fakeData.entitlementData,
],
});
};
window.store = store;
window.selectors = selectors;
window.actions = actions;
window.track = track;
}
if (getConfig().HOTJAR_APP_ID) {
try {
initializeHotjar({
Expand All @@ -70,7 +40,7 @@ export const App = () => {
logError(error);
}
}
}, [authenticatedUser, loadData]);
}, []);
return (
<>
<Helmet>
Expand Down
45 changes: 21 additions & 24 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,24 @@ import { render, screen, waitFor } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';

import { RequestKeys } from 'data/constants/requests';
import { reduxHooks } from 'hooks';
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('data/redux', () => ({
selectors: 'redux.selectors',
actions: 'redux.actions',
thunkActions: 'redux.thunkActions',
}));
jest.mock('hooks', () => ({
reduxHooks: {
useRequestIsFailed: jest.fn(),
usePlatformSettingsData: jest.fn(),
useLoadData: jest.fn(),
},
}));
jest.mock('data/store', () => 'data/store');

jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({})),
Expand All @@ -37,11 +31,15 @@ jest.mock('@edx/frontend-platform/react', () => ({
ErrorPage: () => 'ErrorPage',
}));

const loadData = jest.fn();
reduxHooks.useLoadData.mockReturnValue(loadData);

const supportEmail = 'test@support.com';
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
useInitializeLearnerHome.mockReturnValue({
data: {
platformSettings: {
supportEmail,
},
},
isError: false,
});

describe('App router component', () => {
describe('component', () => {
Expand All @@ -66,7 +64,6 @@ describe('App router component', () => {
describe('no network failure', () => {
beforeEach(() => {
jest.clearAllMocks();
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({});
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
Expand All @@ -79,7 +76,6 @@ describe('App router component', () => {
describe('no network failure with optimizely url', () => {
beforeEach(() => {
jest.clearAllMocks();
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
Expand All @@ -92,7 +88,6 @@ describe('App router component', () => {
describe('no network failure with optimizely project id', () => {
beforeEach(() => {
jest.clearAllMocks();
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
Expand All @@ -105,7 +100,10 @@ describe('App router component', () => {
describe('initialize failure', () => {
beforeEach(() => {
jest.clearAllMocks();
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
useInitializeLearnerHome.mockReturnValue({
data: null,
isError: true,
});
getConfig.mockReturnValue({});
render(<IntlProvider locale="en" messages={messages}><App /></IntlProvider>);
});
Expand All @@ -119,7 +117,6 @@ describe('App router component', () => {
});
describe('refresh failure', () => {
beforeEach(() => {
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
getConfig.mockReturnValue({});
render(<IntlProvider locale="en"><App /></IntlProvider>);
});
Expand Down
2 changes: 2 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const configuration = {
// BASE_URL: process.env.BASE_URL,
APP_ID: process.env.APP_ID,
LMS_BASE_URL: process.env.LMS_BASE_URL,
ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
CREDIT_PURCHASE_URL: process.env.CREDIT_PURCHASE_URL,
Expand All @@ -20,6 +21,7 @@ const configuration = {
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
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',
};

const features = {};
Expand Down
15 changes: 15 additions & 0 deletions src/containers/AppWrapper/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import AppWrapper from './index';

describe('AppWrapper', () => {
it('should render children without modification', () => {
render(
<AppWrapper>
<div>Test Child</div>
</AppWrapper>,
);

expect(screen.getByText('Test Child')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';

import { useIntl } from '@edx/frontend-platform/i18n';
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';

import track from 'tracking';
import { reduxHooks } from 'hooks';
import { useCourseData, useCourseTrackingEvent } from 'hooks';
import { useInitializeLearnerHome } from 'data/hooks';
import useActionDisabledState from '../hooks';
import ActionButton from './ActionButton';
import messages from './messages';

export const BeginCourseButton = ({ cardId }) => {
const { formatMessage } = useIntl();
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
const { data: learnerData } = useInitializeLearnerHome();
const courseData = useCourseData(cardId);
const homeUrl = courseData?.courseRun?.homeUrl;
const execEdTrackingParam = useMemo(() => {
const isExecEd2UCourse = EXECUTIVE_EDUCATION_COURSE_MODES.includes(courseData.enrollment.mode);
const { authOrgId } = learnerData.enterpriseDashboard || {};
return isExecEd2UCourse ? `?org_id=${authOrgId}` : '';
}, [courseData.enrollment.mode, learnerData.enterpriseDashboard]);
const { disableBeginCourse } = useActionDisabledState(cardId);

const handleClick = reduxHooks.useTrackCourseEvent(
const handleClick = useCourseTrackingEvent(
track.course.enterCourseClicked,
cardId,
homeUrl + execEdTrackingParam,
Expand Down
Loading
Loading