feat: added notification UI

This commit is contained in:
sundasnoreen12
2023-05-15 16:36:41 +05:00
parent 2543926c95
commit a5069edd94
13 changed files with 466 additions and 11 deletions

16
package-lock.json generated
View File

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

View File

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

View File

@@ -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 && <NotificationIcon notificationCounts={notificationCounts} />}
{loggedIn ? this.renderUserMenu() : this.renderLoggedOutItems()}
</nav>
</div>
@@ -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);

View File

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

View File

@@ -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 (
<div className="d-flex mx-4 my-3 bell-container">
<OverlayTrigger
trigger="click"
key="bottom"
placement="bottom"
show
overlay={(
<Popover
id="popover-positioned-bottom"
style={{
width: '549px', height: '100vh', marginTop: 34, padding: '32px 0px 24px', maxWidth: '549px',
}}
>
<Popover.Title as="h3" style={{ padding: '0px 26px 16px 24px', border: 'none' }}>
<h3 className="text-primary-500 notification-title"> Notifications </h3>
<div className="setting-icon-container">
<Icon src={Settings} />
</div>
</Popover.Title>
<Popover.Content className="notification-content">
<Tabs defaultActiveKey="discussions" id="uncontrolled-tab-example" className="notification-tabs">
<Tab eventKey="reminders" title="Reminders" notification={10} tabClassName="notification-tab">
Hello I am the first panel.
</Tab>
<Tab eventKey="discussions" title="Discussions" tabClassName="notification-tab">
<NotificationRow />
</Tab>
<Tab eventKey="grades" title="Grades" notification={1} tabClassName="notification-tab">
Hello I am the third panel.
</Tab>
<Tab eventKey="authoringg" title="Authoring" notification={5} tabClassName="notification-tab">
Hello I am the fourth panel.
</Tab>
<Tab eventKey="help" title="Help" notification={10} tabClassName="notification-tab">
Hello I am the fifth panel.
</Tab>
<Tab eventKey="about" title="About" tabClassName="notification-tab">
Hello I am the sixth panel.
</Tab>
</Tabs>
</Popover.Content>
</Popover>
)}
>
<>
<Badge variant="danger position-absolute d-flex flex-row justify-content-center align-items-center">
<Form.Label className="count">{notificationCounts[0]?.count}</Form.Label>
</Badge>
<div className="bell-icon-container">
<IconButton
onClick={() => { handleNotificationTray(!showNotificationTray); }}
onBlur={() => { handleNotificationTray(false); }}
src={NotificationsNone}
iconAs={Icon}
className="d-inline-block align-bottom ml-1 bell-icon"
/>
</div>
</>
</OverlayTrigger>
</div>
);
};
NotificationIcon.propTypes = {
notificationCounts: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.string,
count: PropTypes.string,
})),
};
NotificationIcon.defaultProps = {
notificationCounts: [],
};
export default NotificationIcon;

View File

@@ -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 (
<div className="pt-4">
<div style={{ padding: '10px 24px 10px 24px' }} className="d-flex pb-2">
<span className="w-100 px-0">
{intl.formatMessage(messages.notificationTodayHeading)}
</span>
<span className="w-100 px-0 text-right text-info-500">
{intl.formatMessage(messages.notificationMarkAsRead)}
</span>
</div>
<div>
<NotificationRowItem />
</div>
</div>
);
};
NotificationRow.propTypes = {
};
export default React.memo(NotificationRow);

View File

@@ -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 (
<div style={{ padding: '10px 24px 10px 24px' }} className="d-flex pb-2">
<div style={{ padding: '12px 12px 12px 0px' }} className="mr-2">
<Icon
src={PostOutline}
className="post-summary-comment-count-dimensions mr-0.5"
style={{ height: '28px', width: '28px' }}
/>
</div>
<div className="d-flex w-100">
<div style={{ display: 'contents' }}>
<span className="px-0 text-primary-500 mb-2 w-100" style={{ lineHeight: '24px', width: '417px' }}>
SCM_Lead <span className="text-gray-500">posted </span>
<a
className="text-primary-500"
href="url"
>
Hello and welcome to SC0x!
</a>
</span>
<div
className="d-flex flex-column justify-content-end"
style={{
height: '24px', width: '24px',
}}
>
<div
className="bg-brand-500"
style={{
background: '#D23228', borderRadius: '100px', height: '10px', width: '10px',
}}
/>
</div>
</div>
{/* <div style={{ display: 'contents' }}>
<span className="px-0 text-primary-500 mb-2 w-100" style={{ lineHeight: '24px', width: '417px' }}>
<span className="text-gray-500">Supply Chain Analytics</span>
<span
className="mr-1.5 font-size-8 font-style text-light-700"
style={{ lineHeight: '15px' }}
>
{intl.formatMessage(messages.fullStop)}
</span>
<span>
{timeago.format(postCreatedAt, 'time-locale')}
</span>
</span>
</div> */}
</div>
</div>
);
};
NotificationRowItem.propTypes = {
};
export default React.memo(NotificationRowItem);

View File

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

View File

@@ -0,0 +1,26 @@
import React from 'react';
const HelpOutline = () => (
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_927_733)">
<path
d="M12.8334 20.9997H15.1667V18.6663H12.8334V20.9997ZM14 2.33301C7.56004 2.33301 2.33337 7.55967 2.33337 13.9997C2.33337 20.4397 7.56004 25.6663 14 25.6663C20.44 25.6663 25.6667 20.4397 25.6667 13.9997C25.6667 7.55967 20.44 2.33301 14 2.33301ZM14 23.333C8.85504 23.333 4.66671 19.1447 4.66671 13.9997C4.66671 8.85468 8.85504 4.66634 14 4.66634C19.145 4.66634 23.3334 8.85468 23.3334 13.9997C23.3334 19.1447 19.145 23.333 14 23.333ZM14 6.99967C11.4217 6.99967 9.33337 9.08801 9.33337 11.6663H11.6667C11.6667 10.383 12.7167 9.33301 14 9.33301C15.2834 9.33301 16.3334 10.383 16.3334 11.6663C16.3334 13.9997 12.8334 13.708 12.8334 17.4997H15.1667C15.1667 14.8747 18.6667 14.583 18.6667 11.6663C18.6667 9.08801 16.5784 6.99967 14 6.99967Z"
fill="#00262B"
/>
</g>
<defs>
<clipPath id="clip0_927_733">
<rect width="28" height="28" fill="white" />
</clipPath>
</defs>
</svg>
);
export default HelpOutline;

View File

@@ -0,0 +1,18 @@
import React from 'react';
const PostOutline = () => (
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.66671 4.66634H23.3334V18.6663H6.03171L4.66671 20.0313V4.66634ZM4.66671 2.33301C3.38337 2.33301 2.34504 3.38301 2.34504 4.66634L2.33337 25.6663L7.00004 20.9997H23.3334C24.6167 20.9997 25.6667 19.9497 25.6667 18.6663V4.66634C25.6667 3.38301 24.6167 2.33301 23.3334 2.33301H4.66671ZM7.00004 13.9997H16.3334V16.333H7.00004V13.9997ZM7.00004 10.4997H21V12.833H7.00004V10.4997ZM7.00004 6.99967H21V9.33301H7.00004V6.99967Z"
fill="#00262B"
/>
</svg>
);
export default PostOutline;

View File

@@ -0,0 +1,2 @@
export { default as PostOutline } from './PostOutline';
export { default as HelpOutline } from './HelpOutline';

View File

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

View File

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