feat: load more courses button in notification courses list

This commit is contained in:
Awais Ansari
2023-06-26 18:46:06 +05:00
parent 0f3b7caa0f
commit 1694ea38ab
8 changed files with 92 additions and 69 deletions

View File

@@ -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} />

View File

@@ -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>
);
};

View File

@@ -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>
);

View File

@@ -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:

View File

@@ -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
);

View File

@@ -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;
};

View File

@@ -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) {

View File

@@ -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',
},
});