Skip to content

Commit

Permalink
refactor: plugin slots implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxFrank13 committed Sep 25, 2024
1 parent a174abb commit 9c97d4c
Show file tree
Hide file tree
Showing 24 changed files with 196 additions and 111 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@
"redux-mock-store": "^1.5.4",
"semantic-release": "^20.1.3"
}
}
}
19 changes: 5 additions & 14 deletions src/containers/CoursesPanel/CourseList/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';

import { Pagination } from '@openedx/paragon';
import {
Expand All @@ -8,11 +7,13 @@ import {
import CourseCard from 'containers/CourseCard';

import { useIsCollapsed } from './hooks';
import { useCourseListData } from '../hooks';

export const CourseList = ({
filterOptions, setPageNumber, numPages, showFilters, visibleList,
}) => {
export const CourseList = () => {
const isCollapsed = useIsCollapsed();
const {
filterOptions, setPageNumber, numPages, showFilters, visibleList,
} = useCourseListData();
return (
<>
{showFilters && (
Expand All @@ -38,14 +39,4 @@ export const CourseList = ({
);
};

CourseList.propTypes = {
showFilters: PropTypes.bool.isRequired,
// eslint-disable-next-line react/forbid-prop-types
visibleList: PropTypes.arrayOf(PropTypes.object).isRequired,
// eslint-disable-next-line react/forbid-prop-types
filterOptions: PropTypes.object.isRequired,
numPages: PropTypes.number.isRequired,
setPageNumber: PropTypes.func.isRequired,
};

export default CourseList;
14 changes: 11 additions & 3 deletions src/containers/CoursesPanel/CourseList/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { shallow } from '@edx/react-unit-test-utils';

import { useIsCollapsed } from './hooks';
import CourseList from '.';
import { useCourseListData } from '../hooks';

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

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

jest.mock('containers/CourseCard', () => 'CourseCard');
jest.mock('containers/CourseFilterControls', () => ({
ActiveCourseFilters: 'ActiveCourseFilters',
Expand All @@ -22,9 +27,12 @@ describe('CourseList', () => {
};
useIsCollapsed.mockReturnValue(false);

const createWrapper = (courseListData = defaultCourseListData) => (
shallow(<CourseList {...courseListData} />)
);
const createWrapper = (courseListData = defaultCourseListData) => {
useCourseListData.mockReturnValue(courseListData);
return (
shallow(<CourseList />)
);
};

describe('no courses or filters', () => {
test('snapshot', () => {
Expand Down
18 changes: 2 additions & 16 deletions src/containers/CoursesPanel/__snapshots__/index.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ exports[`CoursesPanel no courses snapshot 1`] = `
<CourseFilterControls />
</div>
</div>
<PluginSlot
id="no_courses_view"
>
<NoCoursesView />
</PluginSlot>
<NoCoursesViewSlot />
</div>
`;

Expand All @@ -44,16 +40,6 @@ exports[`CoursesPanel with courses snapshot 1`] = `
<CourseFilterControls />
</div>
</div>
<PluginSlot
id="course_list"
>
<CourseList
filterOptions={{}}
numPages={1}
setPageNumber={[MockFunction setPageNumber]}
showFilters={false}
visibleList={[]}
/>
</PluginSlot>
<CourseListSlot />
</div>
`;
18 changes: 4 additions & 14 deletions src/containers/CoursesPanel/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { useIntl } from '@edx/frontend-platform/i18n';

import { reduxHooks } from 'hooks';
import {
CourseFilterControls,
} from 'containers/CourseFilterControls';
import NoCoursesView from './NoCoursesView';

import CourseList from './CourseList';
import CourseListSlot from 'plugin-slots/CourseListSlot';
import NoCoursesViewSlot from 'plugin-slots/NoCoursesViewSlot';

import { useCourseListData } from './hooks';

Expand All @@ -35,17 +33,9 @@ export const CoursesPanel = () => {
</div>
</div>
{hasCourses ? (
<PluginSlot
id="course_list"
>
<CourseList {...courseListData} />
</PluginSlot>
<CourseListSlot />
) : (
<PluginSlot
id="no_courses_view"
>
<NoCoursesView />
</PluginSlot>
<NoCoursesViewSlot />
)}
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions src/containers/Dashboard/DashboardLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';

import { Container, Col, Row } from '@openedx/paragon';

import WidgetSidebar from '../WidgetContainers/WidgetSidebar';
import WidgetSidebarSlot from 'plugin-slots/WidgetSidebarSlot';

import hooks from './hooks';

Expand Down Expand Up @@ -41,8 +41,9 @@ export const DashboardLayout = ({ children }) => {
{children}
</Col>
<Col {...columnConfig.sidebar} className="sidebar-column">
{/* TODO: this shouldn't be an h2 but is used for spacing?? */}
{!isCollapsed && (<h2 className="course-list-title">&nbsp;</h2>)}
<WidgetSidebar />
<WidgetSidebarSlot />
</Col>
</Row>
</Container>
Expand Down

This file was deleted.

23 changes: 0 additions & 23 deletions src/containers/WidgetContainers/WidgetSidebar/index.jsx

This file was deleted.

18 changes: 0 additions & 18 deletions src/containers/WidgetContainers/WidgetSidebar/index.test.jsx

This file was deleted.

39 changes: 39 additions & 0 deletions src/plugin-slots/CourseListSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Course List Slot

### Slot ID: `course_list_slot`

## Description

This slot is used for replacing or adding content around the `CourseList` component. The `CourseList` component is only rendered if the learner has enrolled in at least one course.

## Example

The space will show the `CourseList` component by default. This can be disabled in the configuration with the `keepDefault` boolean. Setting the MFE's `env.config.jsx` to the following will replace the default experience with a `CustomCourseList` component.

![Screenshot of the CourseListSlot](./images/course_list_slot.png)

```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import { CustomCourseList } from '<package-that-exports-your-component>'

const config = {
pluginSlots: {
course_list_slot: {
keepDefault: false,
plugins: [
{
op: ops.Insert,
widget: {
id: 'custom_course_list',
type: DIRECT_PLUGIN,
priority: 60,
RenderWidget: CustomCourseList,
},
},
],
},
},
}

export default config;
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/plugin-slots/CourseListSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';
import CourseList from 'containers/CoursesPanel/CourseList';

export const CourseListSlot = () => (
<PluginSlot id="course_list_slot">
<CourseList />
</PluginSlot>
);
export default CourseListSlot;
39 changes: 39 additions & 0 deletions src/plugin-slots/NoCoursesViewSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# No Courses View Slot

### Slot ID: `no_courses_view_slot`

## Description

This slot is used for replacing or adding content around the `NoCoursesView` component, which renders when the learner has not yet enrolled in any courses.

## Example

The space will show the `NoCoursesView` by default. This can be disabled in the configuration with the `keepDefault` boolean. The following `env.config.jsx` will replace the default experience with a `CustomNoCoursesCTA` component.

![Screenshot of the no courses view](./images/no_courses_view_slot.png)

```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import { CustomSidebarPanel } from 'package-that-exports-your-component';

const config = {
pluginSlots: {
no_courses_view_slot: {
keepDefault: false,
plugins: [
{
op: ops.Insert,
widget: {
id: 'custom_no_courses_CTA',
type: DIRECT_PLUGIN,
priority: 60,
RenderWidget: CustomSidebarPanel,
},
},
],
},
},
}

export default config;
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/plugin-slots/NoCoursesViewSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';
import NoCoursesView from 'containers/CoursesPanel/NoCoursesView';

export const NoCoursesViewSlot = () => (
<PluginSlot id="no_courses_view_slot">
<NoCoursesView />
</PluginSlot>
);

export default NoCoursesViewSlot;
3 changes: 3 additions & 0 deletions src/plugin-slots/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# `frontend-app-learner-dashboard` Plugin Slots

* [`footer_slot`](./FooterSlot/)
* [`widget_sidebar_slot`](./WidgetSidebarSlot/)
* [`course_list_slot`](./CourseListSlot/)
* [`no_courses_view_slot`](./NoCoursesViewSlot/)
40 changes: 40 additions & 0 deletions src/plugin-slots/WidgetSidebarSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Widget Sidebar Slot

### Slot ID: `widget_sidebar_slot`

## Description

This slot is used for adding content to the right-hand sidebar.

## Example

The space will show the `LookingForChallengeWidget` by default. This can be disabled in the configuration with the `keepDefault` boolean.
Setting the MFE's `env.config.jsx` to the following will replace the default experience with a `CustomSidebarPanel` component.

![Screenshot of the widget sidebar](./images/looking_for_challenge_widget.png)

```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import { CustomSidebarPanel } from 'package-that-exports-your-component';

const config = {
pluginSlots: {
widget_sidebar_slot: {
keepDefault: false,
plugins: [
{
op: ops.Insert,
widget: {
id: 'custom_sidebar_panel',
type: DIRECT_PLUGIN,
priority: 60,
RenderWidget: CustomSidebarPanel,
},
},
],
},
},
}

export default config;
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9c97d4c

Please sign in to comment.