feat: load more courses button in notification courses list
This commit is contained in:
@@ -35,12 +35,8 @@ subscribe(APP_READY, () => {
|
||||
<Header />
|
||||
<main className="flex-grow-1">
|
||||
<Switch>
|
||||
{allowNotificationRoutes && (
|
||||
<>
|
||||
<Route path="/notifications/:courseId" component={NotificationPreferences} />
|
||||
<Route path="/notifications" component={NotificationCourses} />
|
||||
</>
|
||||
)}
|
||||
{allowNotificationRoutes && (<Route path="/notifications/:courseId" component={NotificationPreferences} />)}
|
||||
{allowNotificationRoutes && (<Route path="/notifications" component={NotificationCourses} />)}
|
||||
<Route path="/id-verification" component={IdVerificationPage} />
|
||||
<Route exact path="/" component={AccountSettingsPage} />
|
||||
<Route path="/notfound" component={NotFoundPage} />
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Container, Icon, Spinner } from '@edx/paragon';
|
||||
import {
|
||||
Container, Icon, Spinner, Button,
|
||||
} from '@edx/paragon';
|
||||
import { ArrowForwardIos } from '@edx/paragon/icons';
|
||||
import { fetchCourseList } from './data/thunks';
|
||||
import { selectCourseListStatus, selectCourseList } from './data/selectors';
|
||||
import { selectCourseListStatus, selectCourseList, selectPagination } from './data/selectors';
|
||||
import {
|
||||
IDLE_STATUS,
|
||||
LOADING_STATUS,
|
||||
@@ -18,11 +20,14 @@ const NotificationCourses = ({ intl }) => {
|
||||
const dispatch = useDispatch();
|
||||
const coursesList = useSelector(selectCourseList());
|
||||
const courseListStatus = useSelector(selectCourseListStatus());
|
||||
const { hasMore, currentPage } = useSelector(selectPagination());
|
||||
|
||||
const loadMore = useCallback((page = 1, pageSize = 10) => {
|
||||
dispatch(fetchCourseList(page, pageSize));
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (courseListStatus === IDLE_STATUS) {
|
||||
dispatch(fetchCourseList());
|
||||
}
|
||||
if (courseListStatus === IDLE_STATUS) { loadMore(); }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -30,43 +35,43 @@ const NotificationCourses = ({ intl }) => {
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
|
||||
if (courseListStatus === LOADING_STATUS) {
|
||||
return (
|
||||
<div className="d-flex h-100">
|
||||
<Spinner
|
||||
variant="primary"
|
||||
animation="border"
|
||||
className="mx-auto my-auto"
|
||||
data-testid="loading-spinner"
|
||||
style={{ width: '4rem', height: '4rem' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Container size="md">
|
||||
<h2 className="notification-heading mt-6 mb-5.5">
|
||||
{intl.formatMessage(messages.notificationHeading)}
|
||||
</h2>
|
||||
<div data-testid="courses-list">
|
||||
{
|
||||
coursesList.map(course => (
|
||||
<Link
|
||||
key={course.id}
|
||||
to={`/notifications/${course.id}`}
|
||||
>
|
||||
<div className="mb-4 d-flex text-gray-700">
|
||||
<span className="ml-0 mr-auto">
|
||||
{course.name}
|
||||
</span>
|
||||
<span className="ml-auto mr-0">
|
||||
<Icon src={ArrowForwardIos} />
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
{coursesList.map(course => (
|
||||
<Link
|
||||
key={course.id}
|
||||
to={`/notifications/${course.id}`}
|
||||
>
|
||||
<div className="mb-4 d-flex text-gray-700">
|
||||
<span className="ml-0 mr-auto">
|
||||
{course.name}
|
||||
</span>
|
||||
<span className="ml-auto mr-0">
|
||||
<Icon src={ArrowForwardIos} />
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
{courseListStatus === LOADING_STATUS ? (
|
||||
<div className="d-flex">
|
||||
<Spinner
|
||||
variant="primary"
|
||||
animation="border"
|
||||
className="mx-auto my-auto"
|
||||
size="lg"
|
||||
data-testid="loading-spinner"
|
||||
/>
|
||||
</div>
|
||||
) : hasMore && (
|
||||
<Button variant="primary" className="w-100 bg-primary-500" onClick={() => loadMore(currentPage + 1)}>
|
||||
{intl.formatMessage(messages.loadMoreCourses)}
|
||||
</Button>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ const NotificationPreferences = () => {
|
||||
const course = useSelector(selectCourse(courseId));
|
||||
const notificationStatus = useSelector(selectNotificationPreferencesStatus());
|
||||
const preferenceAppsIds = useSelector(selectPreferenceAppsId());
|
||||
const isLoading = notificationStatus === LOADING_STATUS || courseStatus === LOADING_STATUS;
|
||||
|
||||
const preferencesList = useMemo(() => (
|
||||
preferenceAppsIds.map(appId => (
|
||||
@@ -47,20 +48,6 @@ const NotificationPreferences = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [courseId]);
|
||||
|
||||
if (notificationStatus === LOADING_STATUS) {
|
||||
return (
|
||||
<div className="d-flex h-100">
|
||||
<Spinner
|
||||
variant="primary"
|
||||
animation="border"
|
||||
className="mx-auto my-auto"
|
||||
style={{ width: '4rem', height: '4rem' }}
|
||||
data-testid="loading-spinner"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(courseStatus === SUCCESS_STATUS && coursesList.length === 0)
|
||||
|| (notificationStatus === FAILURE_STATUS && coursesList.length !== 0)
|
||||
@@ -73,7 +60,7 @@ const NotificationPreferences = () => {
|
||||
<h2 className="notification-heading mt-6 mb-5.5">
|
||||
{intl.formatMessage(messages.notificationHeading)}
|
||||
</h2>
|
||||
<div>
|
||||
<div className="h-100">
|
||||
<div className="d-flex mb-4">
|
||||
<Link to="/notifications">
|
||||
<Icon className="d-inline-block align-bottom ml-1" src={ArrowBack} />
|
||||
@@ -82,7 +69,18 @@ const NotificationPreferences = () => {
|
||||
{course?.name}
|
||||
</span>
|
||||
</div>
|
||||
{ preferencesList }
|
||||
{preferencesList}
|
||||
{isLoading && (
|
||||
<div className="d-flex">
|
||||
<Spinner
|
||||
variant="primary"
|
||||
animation="border"
|
||||
className="mx-auto my-auto"
|
||||
size="lg"
|
||||
data-testid="loading-spinner"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ export const defaultState = {
|
||||
courses: {
|
||||
status: IDLE_STATUS,
|
||||
courses: [],
|
||||
pagination: {},
|
||||
},
|
||||
preferences: {
|
||||
status: IDLE_STATUS,
|
||||
@@ -29,8 +30,8 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
return {
|
||||
...state,
|
||||
courses: {
|
||||
...state.courses,
|
||||
status: LOADING_STATUS,
|
||||
courses: [],
|
||||
},
|
||||
};
|
||||
case Actions.FETCHED_COURSE_LIST:
|
||||
@@ -38,15 +39,16 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
...state,
|
||||
courses: {
|
||||
status: SUCCESS_STATUS,
|
||||
courses: action.payload,
|
||||
courses: [...state.courses.courses, ...action.payload.courseList],
|
||||
pagination: action.payload.pagination,
|
||||
},
|
||||
};
|
||||
case Actions.FAILED_COURSE_LIST:
|
||||
return {
|
||||
...state,
|
||||
courses: {
|
||||
...state.courses,
|
||||
status: FAILURE_STATUS,
|
||||
courses: [],
|
||||
},
|
||||
};
|
||||
case Actions.FETCHING_PREFERENCES:
|
||||
|
||||
@@ -53,3 +53,7 @@ export const selectPreferenceNonEditableChannels = (appId, name) => state => (
|
||||
export const selectSelectedCourseId = () => state => (
|
||||
state.notificationPreferences.preferences.selectedCourse
|
||||
);
|
||||
|
||||
export const selectPagination = () => state => (
|
||||
state.notificationPreferences.courses.pagination
|
||||
);
|
||||
|
||||
@@ -8,9 +8,10 @@ export const getCourseNotificationPreferences = async (courseId) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCourseList = async () => {
|
||||
export const getCourseList = async (page, pageSize) => {
|
||||
const params = snakeCaseObject({ page, pageSize });
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/notifications/enrollments/`;
|
||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
const { data } = await getAuthenticatedHttpClient().get(url, { params });
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,12 +17,24 @@ import {
|
||||
patchPreferenceToggle,
|
||||
} from './service';
|
||||
|
||||
const normalizeCourses = (responseData) => (
|
||||
responseData.map((enrollment) => ({
|
||||
const normalizeCourses = (responseData) => {
|
||||
const courseList = responseData.results?.map((enrollment) => ({
|
||||
id: enrollment.course.id,
|
||||
name: enrollment.course.displayName,
|
||||
}))
|
||||
);
|
||||
})) || [];
|
||||
|
||||
const pagination = {
|
||||
count: responseData.count,
|
||||
currentPage: responseData.currentPage,
|
||||
hasMore: Boolean(responseData.next),
|
||||
totalPages: responseData.numPages,
|
||||
};
|
||||
|
||||
return {
|
||||
courseList,
|
||||
pagination,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizePreferences = (responseData) => {
|
||||
const preferences = responseData.notificationPreferenceConfig;
|
||||
@@ -60,11 +72,11 @@ const normalizePreferences = (responseData) => {
|
||||
return normalizedPreferences;
|
||||
};
|
||||
|
||||
export const fetchCourseList = () => (
|
||||
export const fetchCourseList = (page, pageSize) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(fetchCourseListFetching());
|
||||
const data = await getCourseList();
|
||||
const data = await getCourseList(page, pageSize);
|
||||
const normalizedData = normalizeCourses(camelCaseObject(data));
|
||||
dispatch(fetchCourseListSuccess(normalizedData));
|
||||
} catch (errors) {
|
||||
|
||||
@@ -53,4 +53,9 @@ export const messages = defineMessages({
|
||||
defaultMessage: 'Push',
|
||||
description: 'Display text for push',
|
||||
},
|
||||
loadMoreCourses: {
|
||||
id: 'notification.preference.load.more.courses',
|
||||
defaultMessage: 'Load more courses',
|
||||
description: 'Load more button to load more courses',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user