Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 114 additions & 123 deletions src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import _ from 'lodash'
import { getNotifications, visitNotifications, toggleNotificationSeen, markAllNotificationsRead,
toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications, toggleNotificationsDropdownMobile } from '../../routes/notifications/actions'
import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources, filterOldNotifications } from '../../routes/notifications/helpers/notifications'
toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications,
toggleNotificationsDropdownMobile, hideOlderNotifications } from '../../routes/notifications/actions'
import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources } from '../../routes/notifications/helpers/notifications'
import NotificationsSection from '../NotificationsSection/NotificationsSection'
import NotificationsEmpty from '../NotificationsEmpty/NotificationsEmpty'
import NotificationsDropdownHeader from '../NotificationsDropdownHeader/NotificationsDropdownHeader'
Expand All @@ -18,21 +19,11 @@ import NotificationsMobilePage from './NotificationsMobilePage'
import NotificationsReadAll from './NotificationsReadAll'
import ScrollLock from 'react-scroll-lock-component'
import MediaQuery from 'react-responsive'
import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_DROPDOWN_MAX_TOTAL, REFRESH_NOTIFICATIONS_INTERVAL, SCREEN_BREAKPOINT_MD } from '../../config/constants'
import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_NEW_PER_SOURCE, REFRESH_NOTIFICATIONS_INTERVAL, SCREEN_BREAKPOINT_MD } from '../../config/constants'
import './NotificationsDropdown.scss'

class NotificationsDropdownContainer extends React.Component {

constructor(props) {
super(props)

this.state = {
isViewAll: false
}

this.viewAll = this.viewAll.bind(this)
}

componentDidMount() {
this.props.getNotifications()
this.autoRefreshNotifications = setInterval(() => this.props.getNotifications(), REFRESH_NOTIFICATIONS_INTERVAL)
Expand All @@ -42,6 +33,7 @@ class NotificationsDropdownContainer extends React.Component {
clearInterval(this.autoRefreshNotifications)
// hide notifications dropdown for mobile, when this component is unmounted
this.props.toggleNotificationsDropdownMobile(false)
this.props.hideOlderNotifications()
}

componentWillReceiveProps(nextProps) {
Expand All @@ -52,13 +44,10 @@ class NotificationsDropdownContainer extends React.Component {
// hide notifications dropdown for mobile,
// when this component persist but URL changed
this.props.toggleNotificationsDropdownMobile(false)
this.props.hideOlderNotifications()
}
}

viewAll() {
this.setState({isViewAll: true})
}

render() {
if (!this.props.initialized) {
return <NotificationsDropdown hasUnread={false} />
Expand All @@ -67,10 +56,9 @@ class NotificationsDropdownContainer extends React.Component {
const {lastVisited, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen,
pending, toggleBundledNotificationRead, visitNotifications, oldSourceIds, viewOlderNotifications, isDropdownMobileOpen,
toggleNotificationsDropdownMobile } = this.props
const {isViewAll} = this.state
const getPathname = link => link.split(/[?#]/)[0].replace(/\/?$/, '')

// mark notifications with url mathc current page's url as seen
// mark notifications with url match current page's url as seen
if (!pending) {
const seenNotificationIds = notifications
.filter(({ isRead, seen, goto = '' }) => !isRead && !seen && getPathname(goto) === getPathname(window.location.pathname))
Expand All @@ -80,26 +68,9 @@ class NotificationsDropdownContainer extends React.Component {
}

const notReadNotifications = filterReadNotifications(notifications)
const notOldNotifications = filterOldNotifications(notReadNotifications, oldSourceIds)
const allNotificationsBySources = splitNotificationsBySources(sources, notOldNotifications)
let notificationsBySources

if (!isViewAll) {
notificationsBySources = limitQuantityInSources(
allNotificationsBySources,
NOTIFICATIONS_DROPDOWN_PER_SOURCE,
NOTIFICATIONS_DROPDOWN_MAX_TOTAL
)
} else {
notificationsBySources = allNotificationsBySources
}

const hiddenByLimitCount = _.sumBy(allNotificationsBySources, 'notifications.length') - _.sumBy(notificationsBySources, 'notifications.length')
const allNotificationsBySources = splitNotificationsBySources(sources, notReadNotifications)

const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources
const hasUnread = notReadNotifications.length > 0
const olderNotificationsCount = notReadNotifications.length - notOldNotifications.length
// we have to give Dropdown component some time
// before removing notification item node from the list
// otherwise dropdown thinks we clicked outside and closes dropdown
Expand Down Expand Up @@ -142,92 +113,111 @@ class NotificationsDropdownContainer extends React.Component {

return (
<MediaQuery minWidth={SCREEN_BREAKPOINT_MD}>
{(matches) => (matches ? (
<NotificationsDropdown hasUnread={hasUnread} hasNew={hasNew} onToggle={visitNotifications}>
<NotificationsDropdownHeader onMarkAllClick={() => !pending && markAllNotificationsRead()} hasUnread={hasUnread}/>
{!hasUnread ? (
<div className="notifications-dropdown-body">
{notificationsEmpty}
</div>
) : ([
<ScrollLock key="body">
<div className="notifications-dropdown-body">
{globalSource && globalSource.notifications.length &&
<NotificationsSection
{...globalSource}
isGlobal
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onLinkClick={markNotificationSeen}
/>
}
{projectSources.filter(source => source.notifications.length > 0).map(source => (
<NotificationsSection
{...source}
key={source.id}
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onLinkClick={markNotificationSeen}
/>
))}
</div>
</ScrollLock>,
<NotificationsReadAll key="footer" to="/notifications">
{
olderNotificationsCount > 0 ?
`View ${olderNotificationsCount} older notification${olderNotificationsCount > 1 ? 's' : ''}` :
'View all notifications'
}
</NotificationsReadAll>
])}
</NotificationsDropdown>
) : (
<NotificationsMobilePage
hasUnread={hasUnread}
hasNew={hasNew}
onToggle={() => {
toggleNotificationsDropdownMobile()
visitNotifications()
}}
isOpen={isDropdownMobileOpen}
>
{!hasUnread ? (
notificationsEmpty
) : (
<div>
{globalSource && (globalSource.notifications.length || isViewAll && globalSource.total) &&
<NotificationsSection
{...globalSource}
isGlobal
isSimple
isLoading={globalSource.isLoading}
onReadToggleClick={toggleNotificationReadWithDelay}
onViewOlderClick={isViewAll ? () => viewOlderNotifications(globalSource.id) : null}
onLinkClick={(notificationId) => {
toggleNotificationsDropdownMobile()
markNotificationSeen(notificationId)
}}
/>}
{projectSources.filter(source => source.notifications.length || isViewAll && source.total).map(source => (
<NotificationsSection
{...source}
key={source.id}
isSimple
isLoading={source.isLoading}
onReadToggleClick={toggleNotificationReadWithDelay}
onViewOlderClick={isViewAll ? () => viewOlderNotifications(source.id) : null}
onLinkClick={(notificationId) => {
toggleNotificationsDropdownMobile()
markNotificationSeen(notificationId)
}}
/>
))}
{!isViewAll && (olderNotificationsCount > 0 || hiddenByLimitCount > 0) &&
<NotificationsReadAll onClick={this.viewAll}>Read all notifications</NotificationsReadAll>}
</div>
)}
</NotificationsMobilePage>
))}
{(matches) => {
if (matches) {
const notificationsBySources = limitQuantityInSources(
allNotificationsBySources,
NOTIFICATIONS_DROPDOWN_PER_SOURCE,
oldSourceIds
)
const hiddenByLimitCount = _.sumBy(notificationsBySources, 'total') - _.sumBy(notificationsBySources, 'notifications.length')
const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources

return (
<NotificationsDropdown hasUnread={hasUnread} hasNew={hasNew} onToggle={visitNotifications}>
<NotificationsDropdownHeader onMarkAllClick={() => !pending && markAllNotificationsRead()} hasUnread={hasUnread}/>
{!hasUnread ? (
<div className="notifications-dropdown-body">
{notificationsEmpty}
</div>
) : ([
<ScrollLock key="body">
<div className="notifications-dropdown-body">
{globalSource && globalSource.notifications.length &&
<NotificationsSection
{...globalSource}
isGlobal
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onLinkClick={markNotificationSeen}
/>
}
{projectSources.filter(source => source.notifications.length > 0).map(source => (
<NotificationsSection
{...source}
key={source.id}
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onLinkClick={markNotificationSeen}
/>
))}
</div>
</ScrollLock>,
<NotificationsReadAll key="footer" to="/notifications">
{
hiddenByLimitCount > 0 ?
`View ${hiddenByLimitCount} older notification${hiddenByLimitCount > 1 ? 's' : ''}` :
'View all notifications'
}
</NotificationsReadAll>
])}
</NotificationsDropdown>
)
} else {
const notificationsBySources = limitQuantityInSources(
allNotificationsBySources,
NOTIFICATIONS_NEW_PER_SOURCE,
oldSourceIds
)
const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources

return (
<NotificationsMobilePage
hasUnread={hasUnread}
hasNew={hasNew}
onToggle={() => {
toggleNotificationsDropdownMobile()
visitNotifications()
}}
isOpen={isDropdownMobileOpen}
>
{!hasUnread ? (
notificationsEmpty
) : (
<div>
{globalSource && globalSource.notifications.length > 0 &&
<NotificationsSection
{...globalSource}
isGlobal
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onViewOlderClick={() => viewOlderNotifications(globalSource.id)}
onLinkClick={(notificationId) => {
toggleNotificationsDropdownMobile()
markNotificationSeen(notificationId)
}}
/>}
{projectSources.filter(source => source.notifications.length).map(source => (
<NotificationsSection
{...source}
key={source.id}
isSimple
onReadToggleClick={toggleNotificationReadWithDelay}
onViewOlderClick={() => viewOlderNotifications(source.id)}
onLinkClick={(notificationId) => {
toggleNotificationsDropdownMobile()
markNotificationSeen(notificationId)
}}
/>
))}
</div>
)}
</NotificationsMobilePage>
)
}
}}
</MediaQuery>
)
}
Expand All @@ -243,6 +233,7 @@ const mapDispatchToProps = {
toggleNotificationRead,
toggleBundledNotificationRead,
viewOlderNotifications,
hideOlderNotifications,
toggleNotificationsDropdownMobile
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/ScrollToAnchors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* If there is any hash we check if component has element with such id and scroll to it.
*/
import React from 'react'
import { SCROLL_TO_MARGIN, SCROLL_TO_DURATION } from '../config/constants'
import { SCROLL_TO_MARGIN, SCROLL_TO_DURATION, SCREEN_BREAKPOINT_MD } from '../config/constants'
import { scroller } from 'react-scroll'

/**
Expand All @@ -24,7 +24,7 @@ export function scrollToHash(hash) {
scroller.scrollTo(id, {
spy: true,
smooth: true,
offset: windowWidth < 768 ? 0 : -SCROLL_TO_MARGIN,
offset: windowWidth < SCREEN_BREAKPOINT_MD ? 0 : -SCROLL_TO_MARGIN,
duration: SCROLL_TO_DURATION,
activeClass: 'active'
})
Expand Down
4 changes: 4 additions & 0 deletions src/components/TopBar/SectionToolBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,9 @@
}
}
}

> .section .logo .icon-connect-logo-mono {
margin-top: 0;
}
}
}
5 changes: 2 additions & 3 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const MARK_ALL_NOTIFICATIONS_READ = 'MARK_ALL_NOTIFICATIONS_READ'
export const TOGGLE_NOTIFICATION_READ = 'TOGGLE_NOTIFICATION_READ'
export const TOGGLE_NOTIFICATION_SEEN = 'TOGGLE_NOTIFICATION_SEEN'
export const VIEW_OLDER_NOTIFICATIONS_SUCCESS = 'VIEW_OLDER_NOTIFICATIONS_SUCCESS'
export const HIDE_OLDER_NOTIFICATIONS_SUCCESS = 'HIDE_OLDER_NOTIFICATIONS_SUCCESS'
export const NOTIFICATIONS_PENDING = 'NOTIFICATIONS_PENDING'
export const TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE = 'TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE'

Expand Down Expand Up @@ -379,11 +380,9 @@ export const SORT_OPTIONS = [
export const REFRESH_NOTIFICATIONS_INTERVAL = 1000 * 60 * 1 // 1 minute interval
export const REFRESH_UNREAD_UPDATE_INTERVAL = 1000 * 10 * 1 // 10 second interval
export const NOTIFICATIONS_DROPDOWN_PER_SOURCE = 5
export const NOTIFICATIONS_DROPDOWN_MAX_TOTAL = Infinity
export const NOTIFICATIONS_NEW_PER_SOURCE = 10

export const NOTIFICATIONS_LIMIT = 1000
// old notification time in minutes, a notification is old if its date is later than this time
export const OLD_NOTIFICATION_TIME = 60 * 48 // 2 day2

export const SCROLL_TO_MARGIN = 70 // px - 60px of toolbar height + 10px to make some margin
export const SCROLL_TO_DURATION = 500 // ms
Expand Down
8 changes: 4 additions & 4 deletions src/projects/list/components/Projects/ProjectsGridView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,11 @@ $screen-one-column: 720px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

&:hover {
background-color: $tc-dark-blue-30;
color: $tc-black;
}
.tooltip-target.active .project-customer {
background-color: $tc-dark-blue-30;
color: $tc-black;
}

.project-segment {
Expand Down
5 changes: 5 additions & 0 deletions src/routes/notifications/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
MARK_ALL_NOTIFICATIONS_READ,
TOGGLE_NOTIFICATION_READ,
VIEW_OLDER_NOTIFICATIONS_SUCCESS,
HIDE_OLDER_NOTIFICATIONS_SUCCESS,
NOTIFICATIONS_PENDING,
TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE
} from '../../../config/constants'
Expand Down Expand Up @@ -112,6 +113,10 @@ export const viewOlderNotifications = (sourceId) => (dispatch) => dispatch({
payload: sourceId
})

export const hideOlderNotifications = () => (dispatch) => dispatch({
type: HIDE_OLDER_NOTIFICATIONS_SUCCESS
})

export const toggleNotificationsDropdownMobile = (isOpen) => (dispatch) => dispatch({
type: TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE,
payload: isOpen
Expand Down
Loading