Skip to content

Commit 832e036

Browse files
authored
Merge pull request #167 from topcoder-platform/notifications
Notifications
2 parents cbe727f + 1446074 commit 832e036

File tree

15 files changed

+687
-286
lines changed

15 files changed

+687
-286
lines changed

dist/dev/index.js

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/prod/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/images/icon-bell-grey.svg

Lines changed: 20 additions & 0 deletions
Loading

src/assets/images/icon-checkmark.svg

Lines changed: 12 additions & 0 deletions
Loading

src/assets/sass/_global/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ $green-light: #B5E9DB;
3131
$green-dark: #229174;
3232
$blue: #0596dc;
3333
$blue-btn: #50ADE8;
34+
$blue-link: #0d61bf;
3435

3536
$font-path: "../../assets/fonts";
3637
$img-path: "../../assets/images";

src/components/AccountMenu/index.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import _ from 'lodash'
2-
import React from 'react'
2+
import React, { Fragment } from 'react'
33
import PropTypes from 'prop-types'
44
import cn from 'classnames'
55
import { Link } from 'topcoder-react-utils'
66
import IconAvatar from '../../assets/images/ico-user-default.svg'
77
import IconCloseDark from '../../assets/images/icon-close-dark.svg'
88
import IconSwitchBusiness from '../../assets/images/icon-switch-business.svg'
9+
import IconNotificationsRed from '../../assets/images/icon-bell-red.svg'
10+
import IconNotificationsGrey from '../../assets/images/icon-bell-grey.svg'
11+
import IconRightArrow from '../../assets/images/right-arrow.svg'
912
import styles from './styles.module.scss'
1013
import moment from 'moment'
1114

@@ -43,7 +46,7 @@ class AccountMenu extends React.Component {
4346

4447
render () {
4548
const {
46-
onClose, open, menu, switchText, onSwitch, profile, domRef
49+
onClose, open, menu, switchText, onSwitch, profile, domRef, numNotifications, onClickNotifications
4750
} = this.props
4851

4952
return (
@@ -90,6 +93,34 @@ class AccountMenu extends React.Component {
9093
</div>
9194
)
9295
}
96+
{
97+
!_.isEmpty(profile) && (
98+
<Fragment>
99+
<div
100+
role='button'
101+
className={styles['notifications-mobile']}
102+
onClick={onClickNotifications}
103+
>
104+
<div className={styles['noti-left']}>
105+
{numNotifications > 0
106+
? <IconNotificationsRed className={styles['bell-icon']} />
107+
: <IconNotificationsGrey className={styles['bell-icon']} />
108+
}
109+
<div>
110+
<span className={styles['notifications-title']}>Notifications</span>
111+
{numNotifications > 0 &&
112+
<span className={styles['red-number']}>{'(' + numNotifications + ')'}</span>
113+
}
114+
</div>
115+
</div>
116+
<span role='button' className={styles['icon-open-noti']}>
117+
<IconRightArrow />
118+
</span>
119+
</div>
120+
<span className={styles['noti-separator']} />
121+
</Fragment>
122+
)
123+
}
93124

94125
<div className={styles.menu}>
95126

@@ -100,13 +131,13 @@ class AccountMenu extends React.Component {
100131
))}
101132

102133
</div>
103-
</div>
134+
</div >
104135
)
105136
}
106137
}
107138

108139
AccountMenu.defaultProps = {
109-
numNotifications: 35
140+
numNotifications: 0
110141
}
111142

112143
AccountMenu.propTypes = {
@@ -116,7 +147,9 @@ AccountMenu.propTypes = {
116147
switchText: PropTypes.shape(),
117148
onSwitch: PropTypes.func,
118149
profile: PropTypes.shape(),
119-
domRef: PropTypes.shape()
150+
domRef: PropTypes.shape(),
151+
numNotifications: PropTypes.number,
152+
onClickNotifications: PropTypes.func.isRequired
120153
}
121154

122155
export default AccountMenu

src/components/AccountMenu/styles.module.scss

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,15 @@
7979
}
8080
}
8181

82+
.notifications-mobile {
83+
@include mobile-only;
84+
}
85+
8286
.menu {
8387
@include not-mobile {
8488
padding: 15px 0 25px 0;
8589
}
8690

87-
.notification-mobile {
88-
@include mobile-only;
89-
}
9091

9192
a {
9293
height: 50px;
@@ -186,6 +187,50 @@
186187
}
187188
}
188189

190+
.notifications-mobile {
191+
height: 75px;
192+
display: flex;
193+
align-items: center;
194+
justify-content: space-between;
195+
196+
.noti-left {
197+
198+
display: flex;
199+
align-items: center;
200+
margin-bottom: 2px;
201+
202+
.bell-icon {
203+
margin: 0 10px 0 22px;
204+
}
205+
206+
.red-number {
207+
color: $red;
208+
margin: 0 10px;
209+
}
210+
}
211+
212+
.icon-open-noti {
213+
width: 50px;
214+
height: 50px;
215+
display: flex;
216+
justify-content: center;
217+
align-items: center;
218+
}
219+
220+
a {
221+
color: $white;
222+
font-size: 14px;
223+
line-height: 30px;
224+
}
225+
}
226+
.noti-separator {
227+
background: $gray-05;
228+
width: calc(100% - 46px);
229+
height: 1px;
230+
align-self: center;
231+
padding: 0 $base-unit * 4;
232+
}
233+
189234
.menu {
190235
display: flex;
191236
flex-direction: column;
@@ -200,8 +245,7 @@
200245
padding: 0 $base-unit * 4;
201246
}
202247

203-
a,
204-
.notification-mobile {
248+
a {
205249
color: $gray-90;
206250
height: 40px;
207251
display: flex;
@@ -237,7 +281,6 @@
237281
}
238282
}
239283
}
240-
241284
}
242285
}
243286
}

src/components/LoginNav/index.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@ import NotificationButton from '../NotificationButton'
66
import NotificationsPopup from '../NotificationsPopup'
77
import UserInfo from '../UserInfo'
88
import AccountMenu from '../AccountMenu'
9+
import _ from 'lodash'
910

1011
const LoginNav = ({
1112
loggedIn,
12-
notificationButtonState,
1313
notifications,
1414
accountMenu,
1515
switchText,
1616
onSwitch,
1717
onMenuOpen,
1818
showNotification,
1919
profile,
20-
authURLs
20+
authURLs,
21+
auth,
22+
markNotificationAsRead,
23+
markAllNotificationAsRead,
24+
markAllNotificationAsSeen,
25+
dismissChallengeNotifications
2126
}) => {
2227
const [openNotifications, setOpenNotifications] = useState()
2328
const [openAccountMenu, setOpenAccountMenu] = useState()
@@ -66,19 +71,26 @@ const LoginNav = ({
6671
setOpenAccountMenu(x => !x)
6772
}
6873

74+
// process seenNotifications
75+
const seenNotifications = _.filter((notifications || []), t => !t.isSeen && !t.isRead)
76+
.map(opt => opt.id)
77+
.join('-')
78+
79+
// process unReadNotifications
80+
const unReadNotifications = _.filter((notifications || []), t => !t.isRead).length > 0
81+
6982
const renderLoginPanel = () => {
7083
if (showNotification) {
7184
return ([
7285
<NotificationButton
73-
className={styles.notificationButton}
74-
state={notificationButtonState}
86+
notifications={notifications || []}
7587
notificationsPopupOpen={openNotifications}
7688
onClick={handleClickNotifications}
7789
key='notification-button'
7890
/>,
7991
<UserInfo
8092
profile={profile}
81-
newNotifications={notificationButtonState === 'new'}
93+
newNotifications={!!seenNotifications}
8294
onClick={handleClickUserInfo}
8395
open={openAccountMenu}
8496
key='user-info'
@@ -90,7 +102,7 @@ const LoginNav = ({
90102
return (
91103
<UserInfo
92104
profile={profile}
93-
newNotifications={notificationButtonState === 'new'}
105+
newNotifications={!!seenNotifications}
94106
onClick={handleClickUserInfo}
95107
open={openAccountMenu}
96108
key='user-info'
@@ -117,14 +129,23 @@ const LoginNav = ({
117129
<NotificationsPopup
118130
open={openNotifications}
119131
notifications={notifications}
120-
onClose={() => setOpenNotifications(false)}
132+
onClose={() => {
133+
seenNotifications &&
134+
markAllNotificationAsSeen(seenNotifications, auth.tokenV3)
135+
setOpenNotifications(false)
136+
}}
137+
auth={auth}
138+
unReadNotifications={unReadNotifications}
139+
markNotificationAsRead={markNotificationAsRead}
140+
markAllNotificationAsRead={markAllNotificationAsRead}
141+
dismissChallengeNotifications={dismissChallengeNotifications}
121142
/>
122143
<AccountMenu
123144
profile={profile}
124145
open={openAccountMenu}
125146
menu={accountMenu}
126147
switchText={switchText}
127-
numNotifications={(notifications || []).length}
148+
numNotifications={_.filter((notifications || []), n => !n.isSeen && !n.isRead).length}
128149
onClickNotifications={handleClickNotifications}
129150
onSwitch={onSwitch}
130151
onClose={() => {
@@ -139,15 +160,19 @@ const LoginNav = ({
139160

140161
LoginNav.propTypes = {
141162
loggedIn: PropTypes.bool,
142-
notificationButtonState: PropTypes.string,
143163
notifications: PropTypes.array,
144164
accountMenu: PropTypes.array,
145165
onSwitch: PropTypes.func,
146166
onMenuOpen: PropTypes.func,
147167
showNotification: PropTypes.bool,
148168
profile: PropTypes.shape(),
169+
auth: PropTypes.shape(),
149170
switchText: PropTypes.shape(),
150-
authURLs: PropTypes.shape()
171+
authURLs: PropTypes.shape(),
172+
markNotificationAsRead: PropTypes.func.isRequired,
173+
markAllNotificationAsRead: PropTypes.func.isRequired,
174+
markAllNotificationAsSeen: PropTypes.func.isRequired,
175+
dismissChallengeNotifications: PropTypes.func.isRequired
151176
}
152177

153178
export default LoginNav
Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
33
import cn from 'classnames'
4+
import IconBellEmpty from '../../assets/images/icon-bell-grey.svg'
45
import IconBell from '../../assets/images/icon-bell.svg'
56
import styles from './styles.module.scss'
7+
import _ from 'lodash'
68

7-
const NotificationButton = ({ className, state, onClick, notificationsPopupOpen }) => (
9+
const getNotificationButtonState = (notifications) => {
10+
if (notifications && _.countBy(notifications || [], n => !n.isSeen && !n.isRead).true > 0) {
11+
return 'new'
12+
}
13+
if (notifications.length === 0) {
14+
return 'none'
15+
} else {
16+
return 'seen'
17+
}
18+
}
19+
20+
const NotificationButton = ({ onClick, notificationsPopupOpen, notifications }) => (
821
<div
9-
className={cn(styles.notificationButton, styles[state], notificationsPopupOpen && styles.isNotificationsPopupOpen, className)}
22+
className={cn([styles.notificationButton,
23+
notificationsPopupOpen && styles.isNotificationsPopupOpen,
24+
styles[getNotificationButtonState(notifications)]])}
1025
onClick={onClick}
1126
>
12-
<IconBell />
27+
{(!_.isEmpty(notifications || []) ? <IconBell /> : <IconBellEmpty />)}
1328
</div>
1429
)
1530

1631
NotificationButton.propTypes = {
17-
className: PropTypes.string,
18-
state: PropTypes.oneOf(['none', 'new', 'seen']),
1932
onClick: PropTypes.func,
20-
notificationsPopupOpen: PropTypes.bool
33+
notificationsPopupOpen: PropTypes.bool,
34+
notifications: PropTypes.array
2135
}
2236

2337
export default NotificationButton

0 commit comments

Comments
 (0)