AA-123 welcome message (#121)

This commit is contained in:
Nick
2020-07-23 12:28:56 -04:00
committed by GitHub
parent cdab8959ca
commit f715fd5ed6
10 changed files with 128 additions and 4 deletions

View File

@@ -199,6 +199,7 @@ Object {
"datesWidget": undefined,
"handoutsHtml": "<ul><li>Handout 1</li></ul>",
"id": "course-v1:edX+DemoX+Demo_Course_1",
"welcomeMessageHtml": undefined,
},
},
},

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -0,0 +1,4 @@
.alert-welcome {
border: #b9babe solid 1px !important;
border-left: #000000 solid 3px !important;
}

View File

@@ -9,6 +9,7 @@ export const ALERT_TYPES = {
DANGER: 'danger',
SUCCESS: 'success',
INFO: 'info',
WELCOME: 'welcome',
};
const FLASH_MESSAGES_LOCAL_STORAGE_KEY = 'UserMessagesProvider.flashMessages';