From a8348e156878d9a970689462087a36edbb30ac00 Mon Sep 17 00:00:00 2001 From: Carla Duarte Date: Wed, 28 Jul 2021 09:39:31 -0400 Subject: [PATCH] feat: updating alerts to use Paragon Alert over custom (AA-914) (#557) --- package-lock.json | 22 ++-- package.json | 2 +- .../AccessExpirationAlert.jsx | 6 +- .../AccessExpirationAlertMMP2P.jsx | 6 +- .../AccessExpirationAlertMasquerade.jsx | 6 +- .../enrollment-alert/EnrollmentAlert.jsx | 24 +++-- .../logistration-alert/LogistrationAlert.jsx | 6 +- .../outline-tab/OutlineTab.test.jsx | 8 +- .../course-end-alert/CourseEndAlert.jsx | 6 +- .../course-start-alert/CourseStartAlert.jsx | 8 +- .../PrivateCourseAlert.jsx | 23 ++-- .../alerts/private-course-alert/messages.js | 7 +- .../outline-tab/widgets/WelcomeMessage.jsx | 86 +++++++-------- .../course/course-exit/CourseCelebration.jsx | 56 +++++----- .../course/course-exit/CourseInProgress.jsx | 6 +- .../course/course-exit/CourseNonPassing.jsx | 2 +- .../course/course-exit/ProgramCompletion.jsx | 84 ++++++++------- src/generic/user-messages/Alert.jsx | 102 ++++++------------ src/generic/user-messages/Alert.scss | 3 - src/generic/user-messages/Alert.test.jsx | 55 ---------- .../StreakCelebrationModal.jsx | 26 ++--- 21 files changed, 229 insertions(+), 315 deletions(-) delete mode 100644 src/generic/user-messages/Alert.scss delete mode 100644 src/generic/user-messages/Alert.test.jsx diff --git a/package-lock.json b/package-lock.json index 5a5c0134..108e9d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1447,9 +1447,9 @@ } }, "@edx/paragon": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-16.2.0.tgz", - "integrity": "sha512-13xGUU+BezQ27NvR1gtm7YxbcborgUxX78PlUXjamSM3bqMt4LfviyxZjewyxBuevAF+Tj4PlLzKMLe3SkuwFw==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-16.5.0.tgz", + "integrity": "sha512-oXZBL1eaBKtPnypVx3hAoVs/zwTjQP+jfj6Vk610+GlsoYzevho4H2CXvrym5HKYAvYvKwXuIibh/IOdzhk+tQ==", "requires": { "@fortawesome/fontawesome-svg-core": "^1.2.30", "@fortawesome/free-solid-svg-icons": "^5.14.0", @@ -1467,7 +1467,7 @@ "react-focus-on": "^3.5.0", "react-popper": "^2.2.4", "react-proptype-conditional-require": "^1.0.4", - "react-responsive": "^6.1.1", + "react-responsive": "^8.2.0", "react-table": "^7.6.1", "react-transition-group": "^4.0.0", "tabbable": "^4.0.0", @@ -17689,13 +17689,14 @@ } }, "react-responsive": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-6.1.2.tgz", - "integrity": "sha512-AXentVC/kN3KED9zhzJv2pu4vZ0i6cSHdTtbCScVV1MT6F5KXaG2qs5D7WLmhdaOvmiMX8UfmS4ZSO+WPwDt4g==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz", + "integrity": "sha512-iagCqVrw4QSjhxKp3I/YK6+ODkWY6G+YPElvdYKiUUbywwh9Ds0M7r26Fj2/7dWFFbOpcGnJE6uE7aMck8j5Qg==", "requires": { "hyphenate-style-name": "^1.0.0", "matchmediaquery": "^0.3.0", - "prop-types": "^15.6.1" + "prop-types": "^15.6.1", + "shallow-equal": "^1.1.0" } }, "react-router": { @@ -18985,6 +18986,11 @@ "kind-of": "^6.0.2" } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/package.json b/package.json index d3ea9f84..39bb6e27 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@edx/frontend-enterprise": "4.2.3", "@edx/frontend-lib-special-exams": "1.11.0", "@edx/frontend-platform": "1.11.0", - "@edx/paragon": "16.2.0", + "@edx/paragon": "16.5.0", "@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/free-brands-svg-icons": "5.13.1", "@fortawesome/free-regular-svg-icons": "5.13.1", diff --git a/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx b/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx index db627764..d40d28f7 100644 --- a/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx +++ b/src/alerts/access-expiration-alert/AccessExpirationAlert.jsx @@ -4,9 +4,9 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { FormattedMessage, FormattedDate, injectIntl, intlShape, } from '@edx/frontend-platform/i18n'; -import { Hyperlink } from '@edx/paragon'; +import { Alert, Hyperlink } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; -import { Alert, ALERT_TYPES } from '../../generic/user-messages'; import messages from './messages'; import AccessExpirationAlertMMP2P from './AccessExpirationAlertMMP2P'; import AccessExpirationAlertMasquerade from './AccessExpirationAlertMasquerade'; @@ -100,7 +100,7 @@ function AccessExpirationAlert({ intl, payload }) { } return ( - + + Unlock full course content by {formatDate(upgradeDeadline, 'upgradeTitle')} diff --git a/src/alerts/access-expiration-alert/AccessExpirationAlertMasquerade.jsx b/src/alerts/access-expiration-alert/AccessExpirationAlertMasquerade.jsx index 305cedb3..a71df84f 100644 --- a/src/alerts/access-expiration-alert/AccessExpirationAlertMasquerade.jsx +++ b/src/alerts/access-expiration-alert/AccessExpirationAlertMasquerade.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n'; - -import { Alert, ALERT_TYPES } from '../../generic/user-messages'; +import { Alert } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; function AccessExpirationAlertMasquerade({ payload }) { const { @@ -26,7 +26,7 @@ function AccessExpirationAlertMasquerade({ payload }) { const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; return ( - + + ); return ( - - {text} - {' '} - {button} - {' '} - {loading && } + +
+ {text} + {button} + {loading && } +
); } diff --git a/src/alerts/logistration-alert/LogistrationAlert.jsx b/src/alerts/logistration-alert/LogistrationAlert.jsx index 3271329d..be72180e 100644 --- a/src/alerts/logistration-alert/LogistrationAlert.jsx +++ b/src/alerts/logistration-alert/LogistrationAlert.jsx @@ -2,9 +2,9 @@ import React from 'react'; import { getConfig } from '@edx/frontend-platform'; import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; import { getLoginRedirectUrl } from '@edx/frontend-platform/auth'; -import { Hyperlink } from '@edx/paragon'; +import { Alert, Hyperlink } from '@edx/paragon'; +import { WarningFilled } from '@edx/paragon/icons'; -import { Alert } from '../../generic/user-messages'; import genericMessages from '../../generic/messages'; function LogistrationAlert({ intl }) { @@ -29,7 +29,7 @@ function LogistrationAlert({ intl }) { ); return ( - + { }); await fetchAndRender(); - const alert = await screen.findByText('Welcome to Demonstration Course'); - expect(alert.parentElement).toHaveAttribute('role', 'alert'); + const alert = await screen.findByTestId('private-course-alert'); + expect(alert).toHaveAttribute('role', 'alert'); expect(screen.queryByRole('button', { name: 'Enroll now' })).not.toBeInTheDocument(); expect(screen.getByText('You must be enrolled in the course to see course content.')).toBeInTheDocument(); @@ -495,8 +495,8 @@ describe('Outline Tab', () => { it('displays alert for unenrolled user', async () => { await fetchAndRender(); - const alert = await screen.findByText('Welcome to Demonstration Course'); - expect(alert.parentElement).toHaveAttribute('role', 'alert'); + const alert = await screen.findByTestId('private-course-alert'); + expect(alert).toHaveAttribute('role', 'alert'); expect(screen.getByRole('button', { name: 'Enroll now' })).toBeInTheDocument(); }); diff --git a/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx b/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx index ee2fd80a..3e5d06c5 100644 --- a/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx +++ b/src/course-home/outline-tab/alerts/course-end-alert/CourseEndAlert.jsx @@ -6,8 +6,8 @@ import { FormattedRelative, FormattedTime, } from '@edx/frontend-platform/i18n'; - -import { Alert, ALERT_TYPES } from '../../../../generic/user-messages'; +import { Alert } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; const DAY_MS = 24 * 60 * 60 * 1000; // in ms @@ -78,7 +78,7 @@ function CourseEndAlert({ payload }) { } return ( - + {msg}
{description}
diff --git a/src/course-home/outline-tab/alerts/course-start-alert/CourseStartAlert.jsx b/src/course-home/outline-tab/alerts/course-start-alert/CourseStartAlert.jsx index 51db2091..5304d5fb 100644 --- a/src/course-home/outline-tab/alerts/course-start-alert/CourseStartAlert.jsx +++ b/src/course-home/outline-tab/alerts/course-start-alert/CourseStartAlert.jsx @@ -6,8 +6,8 @@ import { FormattedRelative, FormattedTime, } from '@edx/frontend-platform/i18n'; - -import { Alert, ALERT_TYPES } from '../../../../generic/user-messages'; +import { Alert } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; const DAY_MS = 24 * 60 * 60 * 1000; // in ms @@ -30,7 +30,7 @@ function CourseStartAlert({ payload }) { const delta = new Date(startDate) - new Date(); if (delta < DAY_MS) { return ( - + + {intl.formatMessage(enrollmentMessages.enrollNowInline)} @@ -63,7 +64,7 @@ function PrivateCourseAlert({ intl, payload }) { ); return ( - + {anonymousUser && ( <>

@@ -84,15 +85,11 @@ function PrivateCourseAlert({ intl, payload }) { <>

{intl.formatMessage(outlineMessages.welcomeTo)} {title}

{canEnroll && ( - <> - +
+ {enrollNowButton} + {intl.formatMessage(messages.toAccess)} {loading && } - +
)} {!canEnroll && ( <> diff --git a/src/course-home/outline-tab/alerts/private-course-alert/messages.js b/src/course-home/outline-tab/alerts/private-course-alert/messages.js index 903f92a1..67388a6a 100644 --- a/src/course-home/outline-tab/alerts/private-course-alert/messages.js +++ b/src/course-home/outline-tab/alerts/private-course-alert/messages.js @@ -1,10 +1,11 @@ import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ - enroll: { + toAccess: { id: 'alert.enroll', - defaultMessage: 'You must be enrolled in the course to see course content.', - description: 'Text instructing the learner to enroll in the course in order to see course content.', + defaultMessage: ' to access the full course.', + description: 'Text instructing the learner to enroll in the course in order to see course content. The full string' + + 'would say "Enroll now to access the full course", where "Enroll now" is a button.', }, }); diff --git a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx index 66003935..4cd90511 100644 --- a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx +++ b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx @@ -2,14 +2,13 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Button, TransitionReplace } from '@edx/paragon'; +import { Alert, Button, TransitionReplace } from '@edx/paragon'; import truncate from 'truncate-html'; 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 }) { @@ -27,52 +26,47 @@ function WelcomeMessage({ courseId, intl }) { const messageCanBeShortened = shortWelcomeMessageHtml.length < welcomeMessageHtml.length; const [showShortMessage, setShowShortMessage] = useState(messageCanBeShortened); const dispatch = useDispatch(); - return ( - display && ( - { - setDisplay(false); - dispatch(dismissWelcomeMessage(courseId)); - }} - footer={messageCanBeShortened && ( -
-
- -
-
+ { + setDisplay(false); + dispatch(dismissWelcomeMessage(courseId)); + }} + actions={messageCanBeShortened ? [ + , + ] : []} + > + + {showShortMessage ? ( + + ) : ( + )} - > - - {showShortMessage ? ( - - ) : ( - - )} - - - ) + +
); } diff --git a/src/courseware/course/course-exit/CourseCelebration.jsx b/src/courseware/course/course-exit/CourseCelebration.jsx index 72fae794..87c61b3a 100644 --- a/src/courseware/course/course-exit/CourseCelebration.jsx +++ b/src/courseware/course/course-exit/CourseCelebration.jsx @@ -9,6 +9,7 @@ import { layoutGenerator } from 'react-break'; import { Helmet } from 'react-helmet'; import { useDispatch, useSelector } from 'react-redux'; import { Alert, Button, Hyperlink } from '@edx/paragon'; +import { CheckCircle } from '@edx/paragon/icons'; import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; @@ -285,34 +286,37 @@ function CourseCelebration({ intl }) {
{certHeader && ( - -
-
{certHeader}
- {message} -
- {buttonPrefix} - {buttonLocation && ( - - )} - {buttonSuffix} + +
+
+
{certHeader}
+ {message} +
+ {buttonPrefix} + {buttonLocation && ( + + )} + {buttonSuffix} +
+ {certStatus !== 'unverified' && ( +
+ {`${intl.formatMessage(messages.certificateImage)}`} +
+ )}
- {certStatus !== 'unverified' && ( -
- {`${intl.formatMessage(messages.certificateImage)}`} -
- )}
)} {relatedPrograms && relatedPrograms.map(program => ( diff --git a/src/courseware/course/course-exit/CourseInProgress.jsx b/src/courseware/course/course-exit/CourseInProgress.jsx index 96132f9e..a575d3a9 100644 --- a/src/courseware/course/course-exit/CourseInProgress.jsx +++ b/src/courseware/course/course-exit/CourseInProgress.jsx @@ -34,13 +34,13 @@ function CourseInProgress({ intl }) {
{ intl.formatMessage(messages.courseInProgressHeader) }
- +
-
{ intl.formatMessage(messages.courseInProgressDescription) }
+
{ intl.formatMessage(messages.courseInProgressDescription) }
{datesTabLink && ( + + )} + {type === 'micromasters' && (

+ {intl.formatMessage(messages.microMastersMessage)} + {' '} - {intl.formatMessage(messages.microBachelorsLearnMore)} + {intl.formatMessage(messages.microMastersLearnMore)}

- - - )} - {type === 'micromasters' && ( -

- {intl.formatMessage(messages.microMastersMessage)} - {' '} - - {intl.formatMessage(messages.microMastersLearnMore)} - -

- )} -
-
- {`${intl.formatMessage(messages.certificateImage)}`} + )} +
+
+ {`${intl.formatMessage(messages.certificateImage)}`} +
); diff --git a/src/generic/user-messages/Alert.jsx b/src/generic/user-messages/Alert.jsx index 9f8f7525..b43026db 100644 --- a/src/generic/user-messages/Alert.jsx +++ b/src/generic/user-messages/Alert.jsx @@ -1,82 +1,48 @@ import React from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { - faExclamationTriangle, faInfoCircle, faCheckCircle, faMinusCircle, faTimes, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconButton } from '@edx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { Alert as ParagonAlert } from '@edx/paragon'; +import { CheckCircle, Info, WarningFilled } from '@edx/paragon/icons'; import { ALERT_TYPES } from './UserMessagesProvider'; -import './Alert.scss'; -import messages from '../messages'; -function getAlertClass(type) { - if (type === ALERT_TYPES.ERROR) { - return 'alert-warning'; +function getAlertVariant(type) { + switch (type) { + case ALERT_TYPES.ERROR: + return 'warning'; + case ALERT_TYPES.DANGER: + return 'danger'; + case ALERT_TYPES.SUCCESS: + return 'success'; + default: + return 'info'; } - if (type === ALERT_TYPES.DANGER) { - return 'alert-danger'; - } - if (type === ALERT_TYPES.SUCCESS) { - return 'alert-success'; - } - if (type === ALERT_TYPES.WELCOME) { - return 'alert-welcome alert-light'; - } - return 'alert-info'; } function getAlertIcon(type) { - if (type === ALERT_TYPES.ERROR) { - return faExclamationTriangle; + switch (type) { + case ALERT_TYPES.ERROR: + return WarningFilled; + case ALERT_TYPES.SUCCESS: + return CheckCircle; + default: + return Info; } - if (type === ALERT_TYPES.DANGER) { - return faMinusCircle; - } - if (type === ALERT_TYPES.SUCCESS) { - return faCheckCircle; - } - return faInfoCircle; -} - -function getAlertIconColor(type) { - if (type === ALERT_TYPES.SUCCESS) { - return 'text-success-500'; - } - return ''; } function Alert({ - type, dismissible, children, footer, intl, onDismiss, + type, dismissible, children, onDismiss, stacked, }) { return ( -
-
- {type !== ALERT_TYPES.WELCOME && ( -
- -
- )} -
-
- {children} -
-
- {dismissible && ( -
- -
- )} -
- {footer} -
+ + {children} + ); } @@ -86,20 +52,18 @@ Alert.propTypes = { ALERT_TYPES.DANGER, ALERT_TYPES.INFO, ALERT_TYPES.SUCCESS, - ALERT_TYPES.WELCOME, ]).isRequired, dismissible: PropTypes.bool, children: PropTypes.node, - footer: PropTypes.node, - intl: intlShape.isRequired, onDismiss: PropTypes.func, + stacked: PropTypes.bool, }; Alert.defaultProps = { dismissible: false, children: undefined, - footer: null, onDismiss: null, + stacked: false, }; -export default injectIntl(Alert); +export default Alert; diff --git a/src/generic/user-messages/Alert.scss b/src/generic/user-messages/Alert.scss deleted file mode 100644 index 93d77aa0..00000000 --- a/src/generic/user-messages/Alert.scss +++ /dev/null @@ -1,3 +0,0 @@ -.alert-welcome { - border: #b9babe solid 1px !important; -} diff --git a/src/generic/user-messages/Alert.test.jsx b/src/generic/user-messages/Alert.test.jsx deleted file mode 100644 index 0e08ffde..00000000 --- a/src/generic/user-messages/Alert.test.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { - render, screen, fireEvent, initializeMockApp, -} from '../../setupTest'; -import { Alert, ALERT_TYPES } from './index'; - -describe('Alert', () => { - const types = { - [ALERT_TYPES.ERROR]: { - alert_class: 'alert-warning', - icon: 'fa-exclamation-triangle', - }, - [ALERT_TYPES.DANGER]: { - alert_class: 'alert-danger', - icon: 'fa-minus-circle', - }, - [ALERT_TYPES.SUCCESS]: { - alert_class: 'alert-success', - icon: 'fa-check-circle', - }, - [ALERT_TYPES.INFO]: { - alert_class: 'alert-info', - icon: 'fa-info-circle', - }, - }; - - beforeAll(async () => { - // We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`. - await initializeMockApp(); - }); - - Object.entries(types).forEach(([alert, properties]) => { - it(`renders ${alert} alert`, () => { - const alertContent = 'Test alert.'; - const { container } = render({alertContent}); - - expect(container.firstChild).toHaveClass(properties.alert_class); - expect(container.querySelector('svg')).toHaveClass(properties.icon); - expect(screen.getByText(alertContent)).toBeInTheDocument(); - }); - }); - - it('is dismissible', () => { - const onDismiss = jest.fn(); - const { container } = render(); - - expect(container.firstChild).toHaveClass('alert-dismissible'); - - const dismissButton = screen.getByRole('button'); - expect(container.querySelector('svg')).toHaveClass('fa-exclamation-triangle'); - - fireEvent.click(dismissButton); - expect(onDismiss).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/shared/streak-celebration/StreakCelebrationModal.jsx b/src/shared/streak-celebration/StreakCelebrationModal.jsx index f201cfa3..722be5d1 100644 --- a/src/shared/streak-celebration/StreakCelebrationModal.jsx +++ b/src/shared/streak-celebration/StreakCelebrationModal.jsx @@ -134,18 +134,20 @@ function StreakModal({
)} { AA759ExperimentEnabled && ( - - -
- {intl.formatMessage(messages.congratulations)} -  {intl.formatMessage(messages.streakDiscountMessage)}  - + +
+ +
+ {intl.formatMessage(messages.congratulations)} +  {intl.formatMessage(messages.streakDiscountMessage)}  + +
)}