AA-123 welcome message (#121)
This commit is contained in:
@@ -199,6 +199,7 @@ Object {
|
||||
"datesWidget": undefined,
|
||||
"handoutsHtml": "<ul><li>Handout 1</li></ul>",
|
||||
"id": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"welcomeMessageHtml": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -72,12 +72,14 @@ export async function getOutlineTabData(courseId) {
|
||||
const courseTools = camelCaseObject(data.course_tools);
|
||||
const datesWidget = camelCaseObject(data.dates_widget);
|
||||
const handoutsHtml = data.handouts_html;
|
||||
const welcomeMessageHtml = data.welcome_message_html;
|
||||
|
||||
return {
|
||||
courseTools,
|
||||
courseBlocks,
|
||||
datesWidget,
|
||||
handoutsHtml,
|
||||
welcomeMessageHtml,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,3 +87,8 @@ export async function postCourseDeadlines(courseId) {
|
||||
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`);
|
||||
await getAuthenticatedHttpClient().post(url.href, { course_key: courseId });
|
||||
}
|
||||
|
||||
export async function postDismissWelcomeMessage(courseId) {
|
||||
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/v1/dismiss_welcome_message`);
|
||||
await getAuthenticatedHttpClient().post(url.href, { course_id: courseId });
|
||||
}
|
||||
|
||||
@@ -125,4 +125,16 @@ describe('Data layer integration tests', () => {
|
||||
expect(getTabDataMock).toHaveBeenCalledWith(courseId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test dismissWelcomeMessage', () => {
|
||||
it('Should dismiss welcome message', async () => {
|
||||
const dismissUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/dismiss_welcome_message`;
|
||||
axiosMock.onPost(dismissUrl).reply(201);
|
||||
|
||||
await executeThunk(thunks.dismissWelcomeMessage(courseId), store.dispatch);
|
||||
|
||||
expect(axiosMock.history.post[0].url).toEqual(dismissUrl);
|
||||
expect(axiosMock.history.post[0].data).toEqual(`{"course_id":"${courseId}"}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getOutlineTabData,
|
||||
getProgressTabData,
|
||||
postCourseDeadlines,
|
||||
postDismissWelcomeMessage,
|
||||
} from './api';
|
||||
|
||||
import {
|
||||
@@ -79,3 +80,7 @@ export function resetDeadlines(courseId, getTabData) {
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function dismissWelcomeMessage(courseId) {
|
||||
return async () => postDismissWelcomeMessage(courseId);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import CourseTools from './widgets/CourseTools';
|
||||
import messages from './messages';
|
||||
import Section from './Section';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import WelcomeMessage from './widgets/WelcomeMessage';
|
||||
|
||||
// Note that we import from the component files themselves in the enrollment-alert package.
|
||||
// This is because React.lazy() requires that we import() from a file with a Component as its
|
||||
@@ -61,6 +62,7 @@ function OutlineTab({ intl }) {
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col col-8">
|
||||
<WelcomeMessage courseId={courseId} />
|
||||
{sectionIds.map((sectionId) => (
|
||||
<Section
|
||||
key={sectionId}
|
||||
|
||||
@@ -21,6 +21,18 @@ const messages = defineMessages({
|
||||
id: 'learning.outline.tools',
|
||||
defaultMessage: 'Course Tools',
|
||||
},
|
||||
welcomeMessage: {
|
||||
id: 'learning.outline.welcomeMessage',
|
||||
defaultMessage: 'Welcome Message',
|
||||
},
|
||||
welcomeMessageShowMoreButton: {
|
||||
id: 'learning.outline.welcomeMessageShowMoreButton',
|
||||
defaultMessage: 'Show More',
|
||||
},
|
||||
welcomeMessageShowLessButton: {
|
||||
id: 'learning.outline.welcomeMessageShowLessButton',
|
||||
defaultMessage: 'Show Less',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
68
src/course-home/outline-tab/widgets/WelcomeMessage.jsx
Normal file
68
src/course-home/outline-tab/widgets/WelcomeMessage.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import LmsHtmlFragment from '../LmsHtmlFragment';
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import { Alert } from '../../../generic/user-messages';
|
||||
import { dismissWelcomeMessage } from '../../data/thunks';
|
||||
|
||||
function WelcomeMessage({ courseId, intl }) {
|
||||
const {
|
||||
welcomeMessageHtml,
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
if (!welcomeMessageHtml) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [display, setDisplay] = useState(true);
|
||||
|
||||
const shortWelcomeMessageHtml = welcomeMessageHtml.length > 200 && `${welcomeMessageHtml.substring(0, 199)}...`;
|
||||
const [showShortMessage, setShowShortMessage] = useState(!!shortWelcomeMessageHtml);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
display && (
|
||||
<Alert
|
||||
type="welcome"
|
||||
dismissible
|
||||
onDismiss={() => {
|
||||
setDisplay(false);
|
||||
dispatch(dismissWelcomeMessage(courseId));
|
||||
}}
|
||||
>
|
||||
<div className="my-3">
|
||||
<LmsHtmlFragment
|
||||
html={showShortMessage ? shortWelcomeMessageHtml : welcomeMessageHtml}
|
||||
title={intl.formatMessage(messages.welcomeMessage)}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
shortWelcomeMessageHtml && (
|
||||
<div className="d-flex justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn rounded align-self-center border border-primary bg-white font-weight-bold mb-3"
|
||||
onClick={() => setShowShortMessage(!showShortMessage)}
|
||||
>
|
||||
{showShortMessage ? intl.formatMessage(messages.welcomeMessageShowMoreButton)
|
||||
: intl.formatMessage(messages.welcomeMessageShowLessButton)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Alert>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
WelcomeMessage.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(WelcomeMessage);
|
||||
@@ -8,6 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import { ALERT_TYPES } from './UserMessagesProvider';
|
||||
import './Alert.scss';
|
||||
|
||||
function getAlertClass(type) {
|
||||
if (type === ALERT_TYPES.ERROR) {
|
||||
@@ -19,6 +20,9 @@ function getAlertClass(type) {
|
||||
if (type === ALERT_TYPES.SUCCESS) {
|
||||
return 'alert-success';
|
||||
}
|
||||
if (type === ALERT_TYPES.WELCOME) {
|
||||
return 'alert-welcome alert-light';
|
||||
}
|
||||
return 'alert-info';
|
||||
}
|
||||
|
||||
@@ -41,9 +45,11 @@ function Alert({
|
||||
return (
|
||||
<div className={classNames('alert', { 'alert-dismissible': dismissible }, getAlertClass(type))}>
|
||||
<div className="d-flex align-items-start">
|
||||
<div className="mr-2">
|
||||
<FontAwesomeIcon icon={getAlertIcon(type)} />
|
||||
</div>
|
||||
{type !== ALERT_TYPES.WELCOME && (
|
||||
<div className="mr-2">
|
||||
<FontAwesomeIcon icon={getAlertIcon(type)} />
|
||||
</div>
|
||||
)}
|
||||
<div role="alert" className="flex-grow-1">
|
||||
{children}
|
||||
</div>
|
||||
@@ -54,7 +60,13 @@ function Alert({
|
||||
}
|
||||
|
||||
Alert.propTypes = {
|
||||
type: PropTypes.oneOf([ALERT_TYPES.ERROR, ALERT_TYPES.DANGER, ALERT_TYPES.INFO, ALERT_TYPES.SUCCESS]).isRequired,
|
||||
type: PropTypes.oneOf([
|
||||
ALERT_TYPES.ERROR,
|
||||
ALERT_TYPES.DANGER,
|
||||
ALERT_TYPES.INFO,
|
||||
ALERT_TYPES.SUCCESS,
|
||||
ALERT_TYPES.WELCOME,
|
||||
]).isRequired,
|
||||
dismissible: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
onDismiss: PropTypes.func,
|
||||
|
||||
4
src/generic/user-messages/Alert.scss
Normal file
4
src/generic/user-messages/Alert.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.alert-welcome {
|
||||
border: #b9babe solid 1px !important;
|
||||
border-left: #000000 solid 3px !important;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export const ALERT_TYPES = {
|
||||
DANGER: 'danger',
|
||||
SUCCESS: 'success',
|
||||
INFO: 'info',
|
||||
WELCOME: 'welcome',
|
||||
};
|
||||
|
||||
const FLASH_MESSAGES_LOCAL_STORAGE_KEY = 'UserMessagesProvider.flashMessages';
|
||||
|
||||
Reference in New Issue
Block a user