AA-264 mfe courseware reset dates (#165)
- add toast display after successful reset of dates via banner.
This commit is contained in:
@@ -5,6 +5,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": null,
|
||||
"courseStatus": "loading",
|
||||
"displayResetDatesToast": false,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
@@ -21,6 +22,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"displayResetDatesToast": false,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
@@ -104,6 +106,7 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"displayResetDatesToast": false,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
|
||||
@@ -10,6 +10,7 @@ const slice = createSlice({
|
||||
initialState: {
|
||||
courseStatus: 'loading',
|
||||
courseId: null,
|
||||
displayResetDatesToast: false,
|
||||
},
|
||||
reducers: {
|
||||
fetchTabRequest: (state, { payload }) => {
|
||||
@@ -24,6 +25,9 @@ const slice = createSlice({
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = FAILED;
|
||||
},
|
||||
toggleResetDatesToast: (state, { payload }) => {
|
||||
state.displayResetDatesToast = payload.displayResetDatesToast;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,6 +35,7 @@ export const {
|
||||
fetchTabRequest,
|
||||
fetchTabSuccess,
|
||||
fetchTabFailure,
|
||||
toggleResetDatesToast,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
fetchTabFailure,
|
||||
fetchTabRequest,
|
||||
fetchTabSuccess,
|
||||
toggleResetDatesToast,
|
||||
} from './slice';
|
||||
|
||||
export function fetchTab(courseId, tab, getTabData) {
|
||||
@@ -78,6 +79,7 @@ export function resetDeadlines(courseId, getTabData) {
|
||||
return async (dispatch) => {
|
||||
postCourseDeadlines(courseId).then(() => {
|
||||
dispatch(getTabData(courseId));
|
||||
dispatch(toggleResetDatesToast({ displayResetDatesToast: true }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {
|
||||
useState,
|
||||
useLayoutEffect,
|
||||
} from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -12,6 +13,8 @@ import messages from './messages';
|
||||
import BookmarkButton from '../bookmark/BookmarkButton';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import PageLoading from '../../../generic/PageLoading';
|
||||
import { resetDeadlines } from '../../../course-home/data/thunks';
|
||||
import { fetchCourse } from '../../data/thunks';
|
||||
|
||||
const LockPaywall = React.lazy(() => import('./lock-paywall'));
|
||||
|
||||
@@ -64,6 +67,8 @@ function Unit({
|
||||
contentTypeGatingEnabled,
|
||||
} = course;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Do not remove this hook. See function description.
|
||||
useLoadBearingHook(id);
|
||||
|
||||
@@ -130,6 +135,13 @@ function Unit({
|
||||
height={iframeHeight}
|
||||
scrolling="no"
|
||||
referrerPolicy="origin"
|
||||
onLoad={() => {
|
||||
window.onmessage = function (e) {
|
||||
if (e.data === 'reset_dates') {
|
||||
dispatch(resetDeadlines(courseId, fetchCourse));
|
||||
}
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { Header } from '../course-header';
|
||||
import PageLoading from '../generic/PageLoading';
|
||||
|
||||
import messages from './messages';
|
||||
import LoadedTabPage from './LoadedTabPage';
|
||||
import LearningToast from '../toast/LearningToast';
|
||||
import { toggleResetDatesToast } from '../course-home/data/slice';
|
||||
|
||||
function TabPage({
|
||||
intl,
|
||||
courseStatus,
|
||||
...passthroughProps
|
||||
}) {
|
||||
const courseId = useSelector(state => state.courseHome.courseId || state.courseware.courseId);
|
||||
const {
|
||||
displayResetDatesToast,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (courseStatus === 'loading') {
|
||||
return (
|
||||
<>
|
||||
@@ -26,7 +35,16 @@ function TabPage({
|
||||
|
||||
if (courseStatus === 'loaded') {
|
||||
return (
|
||||
<LoadedTabPage {...passthroughProps} />
|
||||
<>
|
||||
<LearningToast
|
||||
body={messages.datesResetSuccessBody}
|
||||
header={messages.datesResetSuccessHeader}
|
||||
link={`/course/${courseId}/dates`}
|
||||
onClose={() => dispatch(toggleResetDatesToast({ displayResetDatesToast: false }))}
|
||||
show={displayResetDatesToast}
|
||||
/>
|
||||
<LoadedTabPage {...passthroughProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@ const messages = defineMessages({
|
||||
id: 'learning.loading',
|
||||
defaultMessage: 'Loading course page…',
|
||||
},
|
||||
datesResetSuccessBody: {
|
||||
id: 'learning.datesResetSuccessBody',
|
||||
defaultMessage: 'View all dates',
|
||||
},
|
||||
datesResetSuccessHeader: {
|
||||
id: 'learning.datesResetSuccessHeader',
|
||||
defaultMessage: 'Your due dates have been successfully shifted to help you stay on track.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
57
src/toast/LearningToast.jsx
Normal file
57
src/toast/LearningToast.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Toast } from '@edx/paragon';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import './LearningToast.scss';
|
||||
|
||||
function LearningToast({
|
||||
intl,
|
||||
body,
|
||||
header,
|
||||
link,
|
||||
onClose,
|
||||
show,
|
||||
}) {
|
||||
return (
|
||||
<Toast
|
||||
onClose={onClose}
|
||||
show={show}
|
||||
delay={3000}
|
||||
autohide
|
||||
style={{
|
||||
boxShadow: 'none',
|
||||
position: 'fixed',
|
||||
bottom: '1rem',
|
||||
left: '1rem',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<Toast.Header className="bg-gray-700 border-bottom-0 text-light">
|
||||
<div className="mr-auto">{intl.formatMessage(header)}</div>
|
||||
</Toast.Header>
|
||||
<Toast.Body className="bg-gray-700 text-light">
|
||||
<Link className="text-light" to={link}>{intl.formatMessage(body)}</Link>
|
||||
</Toast.Body>
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
|
||||
LearningToast.propTypes = {
|
||||
body: PropTypes.shape({
|
||||
possible: PropTypes.number,
|
||||
earned: PropTypes.number,
|
||||
graded: PropTypes.bool,
|
||||
}).isRequired,
|
||||
header: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
defaultMessage: PropTypes.string,
|
||||
}).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(LearningToast);
|
||||
19
src/toast/LearningToast.scss
Normal file
19
src/toast/LearningToast.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
.toast-header {
|
||||
div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
align-self: flex-start;
|
||||
color: #FFFFFF;
|
||||
&:hover {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user