From a5069edd94e12315a7a0efed962f951310593c6a Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Mon, 15 May 2023 16:36:41 +0500 Subject: [PATCH] feat: added notification UI --- package-lock.json | 16 +-- package.json | 4 +- src/DesktopHeader.jsx | 8 ++ src/Header.jsx | 24 ++++ src/Notifications/NotificationIcon.jsx | 90 +++++++++++++++ src/Notifications/NotificationRow.jsx | 29 +++++ src/Notifications/NotificationRowItem.jsx | 69 ++++++++++++ src/Notifications/data/api.js | 38 +++++++ src/Notifications/icons/HelpOutline.jsx | 26 +++++ src/Notifications/icons/PostOutline.jsx | 18 +++ src/Notifications/icons/index.js | 2 + src/Notifications/messages.js | 25 +++++ src/index.scss | 128 +++++++++++++++++++++- 13 files changed, 466 insertions(+), 11 deletions(-) create mode 100644 src/Notifications/NotificationIcon.jsx create mode 100644 src/Notifications/NotificationRow.jsx create mode 100644 src/Notifications/NotificationRowItem.jsx create mode 100644 src/Notifications/data/api.js create mode 100644 src/Notifications/icons/HelpOutline.jsx create mode 100644 src/Notifications/icons/PostOutline.jsx create mode 100644 src/Notifications/icons/index.js create mode 100644 src/Notifications/messages.js diff --git a/package-lock.json b/package-lock.json index 0e41f33..8817521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0-semantically-released", "license": "AGPL-3.0", "dependencies": { - "@edx/paragon": "20.36.0", + "@edx/paragon": "20.34.0", "@fortawesome/fontawesome-svg-core": "6.3.0", "@fortawesome/free-brands-svg-icons": "6.3.0", "@fortawesome/free-regular-svg-icons": "6.3.0", @@ -25,7 +25,7 @@ "@edx/frontend-build": "12.8.27", "@edx/frontend-platform": "4.2.0", "@edx/reactifex": "^2.1.1", - "@testing-library/dom": "9.3.0", + "@testing-library/dom": "9.2.0", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "10.4.9", "enzyme": "3.11.0", @@ -3144,9 +3144,9 @@ } }, "node_modules/@edx/paragon": { - "version": "20.36.0", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.36.0.tgz", - "integrity": "sha512-0dn4r1HvcrHY66xmLkLTRIBD09TDrNn6vxWu1XZr2SwkGLf56cI8aGkZEySeOVs/VLWtJRMmMJaSbozCpxvLyg==", + "version": "20.34.0", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.34.0.tgz", + "integrity": "sha512-elWDy17qAHsORsqhAyp1SFOmnwKqvgHJrOvoZnw03xXUeHMn7m4j5aH3UIAHHYV/xu2au4g0YSnwni/+KDLP2A==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", @@ -6000,9 +6000,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz", - "integrity": "sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz", + "integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", diff --git a/package.json b/package.json index 0565551..f3ccc59 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@edx/frontend-build": "12.8.27", "@edx/frontend-platform": "4.2.0", "@edx/reactifex": "^2.1.1", - "@testing-library/dom": "9.3.0", + "@testing-library/dom": "9.2.0", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "10.4.9", "enzyme": "3.11.0", @@ -56,7 +56,7 @@ "redux-saga": "1.2.3" }, "dependencies": { - "@edx/paragon": "20.36.0", + "@edx/paragon": "20.34.0", "@fortawesome/fontawesome-svg-core": "6.3.0", "@fortawesome/free-brands-svg-icons": "6.3.0", "@fortawesome/free-regular-svg-icons": "6.3.0", diff --git a/src/DesktopHeader.jsx b/src/DesktopHeader.jsx index 8bd142f..d7d5b62 100644 --- a/src/DesktopHeader.jsx +++ b/src/DesktopHeader.jsx @@ -13,6 +13,7 @@ import messages from './Header.messages'; // Assets import { CaretIcon } from './Icons'; +import NotificationIcon from './Notifications/NotificationIcon'; class DesktopHeader extends React.Component { constructor(props) { // eslint-disable-line no-useless-constructor @@ -121,6 +122,7 @@ class DesktopHeader extends React.Component { loggedIn, intl, appMenu, + notificationCounts, } = this.props; const logoProps = { src: logo, alt: logoAltText, href: logoDestination }; const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'mw-100' : null; @@ -149,6 +151,7 @@ class DesktopHeader extends React.Component { aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="nav secondary-menu-container align-items-center ml-auto" > + {loggedIn && } {loggedIn ? this.renderUserMenu() : this.renderLoggedOutItems()} @@ -179,6 +182,10 @@ DesktopHeader.propTypes = { avatar: PropTypes.string, username: PropTypes.string, loggedIn: PropTypes.bool, + notificationCounts: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + count: PropTypes.string, + })), // i18n intl: intlShape.isRequired, @@ -209,6 +216,7 @@ DesktopHeader.defaultProps = { username: null, loggedIn: false, appMenu: null, + notificationCounts: [], }; export default injectIntl(DesktopHeader); diff --git a/src/Header.jsx b/src/Header.jsx index c0db257..da435a0 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -88,6 +88,29 @@ const Header = ({ intl }) => { }, ]; + const notificationCounts = [ + { + type: 'total', + count: 25, + }, + { + type: 'reminders', + count: 1, + }, + { + type: 'discussions', + count: 0, + }, + { + type: 'grades', + count: 0, + }, + { + type: 'authoring', + count: 24, + }, + ]; + const props = { logo: config.LOGO_URL, logoAltText: config.SITE_NAME, @@ -98,6 +121,7 @@ const Header = ({ intl }) => { mainMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu, userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu, loggedOutItems: getConfig().AUTHN_MINIMAL_HEADER ? [] : loggedOutItems, + notificationCounts, }; return ( diff --git a/src/Notifications/NotificationIcon.jsx b/src/Notifications/NotificationIcon.jsx new file mode 100644 index 0000000..85ab5cf --- /dev/null +++ b/src/Notifications/NotificationIcon.jsx @@ -0,0 +1,90 @@ +import React, { useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { NotificationsNone, Settings } from '@edx/paragon/icons'; +import { + Tabs, Tab, Badge, Form, Icon, IconButton, OverlayTrigger, Popover, +} from '@edx/paragon'; +import NotificationRow from './NotificationRow'; + +const NotificationIcon = ({ notificationCounts }) => { + const [showNotificationTray, setShowNotificationTray] = useState(false); + + const handleNotificationTray = useCallback((value) => { + setShowNotificationTray(value); + }, []); + + return ( +
+ + +

Notifications

+
+ +
+
+ + + + Hello I am the first panel. + + + + + + Hello I am the third panel. + + + Hello I am the fourth panel. + + + Hello I am the fifth panel. + + + Hello I am the sixth panel. + + + + + )} + > + <> + + {notificationCounts[0]?.count} + +
+ { handleNotificationTray(!showNotificationTray); }} + onBlur={() => { handleNotificationTray(false); }} + src={NotificationsNone} + iconAs={Icon} + className="d-inline-block align-bottom ml-1 bell-icon" + /> +
+ +
+
+ ); +}; + +NotificationIcon.propTypes = { + notificationCounts: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + count: PropTypes.string, + })), +}; + +NotificationIcon.defaultProps = { + notificationCounts: [], +}; +export default NotificationIcon; diff --git a/src/Notifications/NotificationRow.jsx b/src/Notifications/NotificationRow.jsx new file mode 100644 index 0000000..9618ab8 --- /dev/null +++ b/src/Notifications/NotificationRow.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { messages } from './messages'; +import NotificationRowItem from './NotificationRowItem'; + +const NotificationRow = () => { + const intl = useIntl(); + + return ( +
+
+ + {intl.formatMessage(messages.notificationTodayHeading)} + + + {intl.formatMessage(messages.notificationMarkAsRead)} + +
+
+ +
+
+ ); +}; + +NotificationRow.propTypes = { +}; + +export default React.memo(NotificationRow); diff --git a/src/Notifications/NotificationRowItem.jsx b/src/Notifications/NotificationRowItem.jsx new file mode 100644 index 0000000..bd6688d --- /dev/null +++ b/src/Notifications/NotificationRowItem.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Icon } from '@edx/paragon'; +// import * as timeago from 'timeago.js'; +import { messages } from './messages'; +import { PostOutline } from './icons'; + +const NotificationRowItem = () => { + const intl = useIntl(); + + return ( +
+
+ +
+
+
+ + SCM_Lead posted + + Hello and welcome to SC0x! + + +
+
+
+
+ {/*
+ + Supply Chain Analytics + + {intl.formatMessage(messages.fullStop)} + + + {timeago.format(postCreatedAt, 'time-locale')} + + + + +
*/} +
+
+ ); +}; + +NotificationRowItem.propTypes = { +}; + +export default React.memo(NotificationRowItem); diff --git a/src/Notifications/data/api.js b/src/Notifications/data/api.js new file mode 100644 index 0000000..18398ba --- /dev/null +++ b/src/Notifications/data/api.js @@ -0,0 +1,38 @@ +/* eslint-disable import/prefer-default-export */ +// import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig } from '@edx/frontend-platform'; + +export const getApiBaseUrl = () => getConfig().LMS_BASE_URL; + +export async function getCourseTopics() { + // const url = `${getApiBaseUrl()}/api/discussion/v1/notifications/`; + + // const { data } = await getAuthenticatedHttpClient() + // .get(url); + + const data = [{ + TODAY: [ + { + type: 'post', + respondingUser: 'SCM_Lead', + notificationContent: 'Hello and welcome to SC0x!', + targetUser: '', + courseName: 'Supply Chain Analytics', + URL: '', + status: 'unread', + time: '15m', + }, + { + type: 'help', + respondingUser: 'MITx_Learner', + notificationContent: 'What grade does a student need to get in order to pass the course and earn a certificate?', + targetUser: '', + courseName: 'Supply Chain Analytics', + URL: '', + status: 'unread', + time: '15m', + }, + ], + }]; + return data; +} diff --git a/src/Notifications/icons/HelpOutline.jsx b/src/Notifications/icons/HelpOutline.jsx new file mode 100644 index 0000000..53cdd48 --- /dev/null +++ b/src/Notifications/icons/HelpOutline.jsx @@ -0,0 +1,26 @@ +import React from 'react'; + +const HelpOutline = () => ( + + + + + + + + + + + +); + +export default HelpOutline; diff --git a/src/Notifications/icons/PostOutline.jsx b/src/Notifications/icons/PostOutline.jsx new file mode 100644 index 0000000..a7bb4d4 --- /dev/null +++ b/src/Notifications/icons/PostOutline.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const PostOutline = () => ( + + + +); + +export default PostOutline; diff --git a/src/Notifications/icons/index.js b/src/Notifications/icons/index.js new file mode 100644 index 0000000..c27b06d --- /dev/null +++ b/src/Notifications/icons/index.js @@ -0,0 +1,2 @@ +export { default as PostOutline } from './PostOutline'; +export { default as HelpOutline } from './HelpOutline'; diff --git a/src/Notifications/messages.js b/src/Notifications/messages.js new file mode 100644 index 0000000..e7a3db8 --- /dev/null +++ b/src/Notifications/messages.js @@ -0,0 +1,25 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +// eslint-disable-next-line import/prefer-default-export +export const messages = defineMessages({ + notificationTodayHeading: { + id: 'notification.today.heading', + defaultMessage: 'Today', + description: 'Today Notifications', + }, + notificationMarkAsRead: { + id: 'notification.mark.as.read', + defaultMessage: 'Mark all as read', + description: 'Mark all Notifications as read', + }, + notificationPostedContent: { + id: 'notification.posted.content', + defaultMessage: '{respondingUser} posted {notificationContent}', + description: 'Display notification content for post type', + }, + notificationHelpedContent: { + id: 'notification.helped.content', + defaultMessage: '{respondingUser} asked {notificationContent}', + description: 'Display notification content for help type', + }, +}); diff --git a/src/index.scss b/src/index.scss index 8d5d162..ae08e2c 100644 --- a/src/index.scss +++ b/src/index.scss @@ -27,7 +27,7 @@ $white: #fff; .learning-header { min-width: 0; - + .course-title-lockup { min-width: 0; @@ -118,3 +118,129 @@ $white: #fff; border-radius: $rounded-pill; } } + +.popover .arrow{ + display: none !important; + } +.notification-title{ + line-height: 24px; + font-weight: 700; + font-size: 18px; +} +.setting-icon-container{ + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: flex-end; + width: 100%; + position: absolute; + margin-left: -40px; + margin-top: -7px; + span{ + height:20px; + width: 20px; + } +} + +.notification-content{ + padding-left: 0px; + width: 549px; + } +.notification-tabs{ + height: 38px; + width:549px; + padding-left: 12px; + button{ + font-size: 14px; + } + .dropdown-toggle{ + height: 36px; + padding-top: 0px !important; + padding-left: 12px !important; + padding-right: 26px !important; + + div{ + margin-top: 4px; + height: 20px; + width: 20px; + } + } + .dropdown{ + height: 36px; + } + .notification-tab, .dropdown-item{ + display: flex; + flex-direction: row; + align-items: center; + padding: 0px 12px 12px !important; + height: 36px; + font-size:14px; + font-weight: 500; + line-height: 24px; + +} +.expandable{ + height: 20px; + width: 20px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 6px 7px; + gap: 8px; + position: relative; + margin-left: 4px; + } +} + +.bell-container{ + + button{ + position: relative; + background: transparent; + color: black; + border: none; + &:hover, &:active, &:focus{ + background: transparent !important; + color: black !important; + border: none; + } + &::before{ + border: none !important + } + } + .bell-icon-container{ + width: 36px; + height: 36px; + border-radius: 1e+16px; + color: black !important; + &:hover{ + background: #EAE6E5; + } + .bell-icon{ + margin-left: -4px !important; + &:focus{ + box-shadow: none !important + } + } + + } + .badge{ + z-index: 1; + border-radius: 54px; + border: 2px solid #FFFFFF; + padding: 4px 5px; + width: 23px; + height: 16px; + margin-top: 3px; + margin-left: 20px; + .count{ + font-size: 9px; + line-height: 20px; + width: 13px; + height: 8px; + font-weight: 600; + margin-top: -3px; + } + } +}