add the container for the notifications dropdown
This commit is contained in:
parent
322da431eb
commit
64ffc78f45
8 changed files with 156 additions and 28 deletions
app
frontend/src
Metamaps/GlobalUI
components/App
|
@ -13,6 +13,45 @@ $unread_notifications_dot_size: 8px;
|
||||||
.notificationsIcon {
|
.notificationsIcon {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notificationsBox {
|
||||||
|
position: absolute;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 350px;
|
||||||
|
right: 0;
|
||||||
|
top: 50px;
|
||||||
|
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
|
||||||
|
|
||||||
|
.notificationsBoxTriangle {
|
||||||
|
min-width: 0 !important;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 48px;
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
margin-left: -10px;
|
||||||
|
top: -10px;
|
||||||
|
border-top: 1px solid rgba(49, 72, 67, 0.11) !important;
|
||||||
|
border-left: 1px solid rgba(49, 72, 67, 0.11) !important;
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
border-right: 0 !important;
|
||||||
|
background-color: #fff;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsBoxSeeAll {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-family: din-regular, helvetica, sans-serif;
|
||||||
|
color: #4fb5c0;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.controller-notifications {
|
.controller-notifications {
|
||||||
|
@ -66,12 +105,12 @@ $unread_notifications_dot_size: 8px;
|
||||||
|
|
||||||
& > a {
|
& > a {
|
||||||
float: left;
|
float: left;
|
||||||
width: 85%;
|
width: 85%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-actor {
|
.notification-actor {
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -97,7 +136,7 @@ $unread_notifications_dot_size: 8px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-date {
|
.notification-date {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -129,7 +168,7 @@ $unread_notifications_dot_size: 8px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.notificationPage {
|
.notificationPage {
|
||||||
.thirty-two-avatar {
|
.thirty-two-avatar {
|
||||||
|
@ -139,14 +178,14 @@ $unread_notifications_dot_size: 8px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.decline {
|
&.decline {
|
||||||
background: #DB5D5D;
|
background: #DB5D5D;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -154,7 +193,7 @@ $unread_notifications_dot_size: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-body {
|
.notification-body {
|
||||||
p, div {
|
p, div {
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
|
|
|
@ -10,10 +10,15 @@ class NotificationsController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.json do
|
format.json do
|
||||||
render json: @notifications.map do |notification|
|
notifications = @notifications.map do |notification|
|
||||||
receipt = @receipts.find_by(notification_id: notification.id)
|
receipt = @receipts.find_by(notification_id: notification.id)
|
||||||
notification.as_json.merge(is_read: receipt.is_read)
|
notification.as_json.merge(
|
||||||
|
is_read: receipt.is_read,
|
||||||
|
notified_object: notification.notified_object,
|
||||||
|
sender: notification.sender
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
render json: notifications
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
14
frontend/src/Metamaps/GlobalUI/Notifications.js
Normal file
14
frontend/src/Metamaps/GlobalUI/Notifications.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const Notifications = {
|
||||||
|
notifications: null,
|
||||||
|
fetch: render => {
|
||||||
|
$.ajax({
|
||||||
|
url: '/notifications.json',
|
||||||
|
success: function(data) {
|
||||||
|
Notifications.notifications = data
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notifications
|
|
@ -8,6 +8,7 @@ import apply from 'async/apply'
|
||||||
|
|
||||||
import { notifyUser } from './index.js'
|
import { notifyUser } from './index.js'
|
||||||
import ImportDialog from './ImportDialog'
|
import ImportDialog from './ImportDialog'
|
||||||
|
import Notifications from './Notifications'
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views'
|
import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views'
|
||||||
|
@ -107,7 +108,9 @@ const ReactApp = {
|
||||||
mobileTitleWidth: self.mobileTitleWidth,
|
mobileTitleWidth: self.mobileTitleWidth,
|
||||||
mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e),
|
mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e),
|
||||||
openInviteLightbox: () => self.openLightbox('invite'),
|
openInviteLightbox: () => self.openLightbox('invite'),
|
||||||
serverData: self.serverData
|
serverData: self.serverData,
|
||||||
|
notifications: Notifications.notifications,
|
||||||
|
fetchNotifications: apply(Notifications.fetch, ReactApp.render)
|
||||||
},
|
},
|
||||||
self.getMapProps(),
|
self.getMapProps(),
|
||||||
self.getTopicProps(),
|
self.getTopicProps(),
|
||||||
|
|
41
frontend/src/components/App/NotificationBox.js
Normal file
41
frontend/src/components/App/NotificationBox.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import onClickOutsideAddon from 'react-onclickoutside'
|
||||||
|
|
||||||
|
class NotificationBox extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
notifications: PropTypes.array,
|
||||||
|
fetchNotifications: PropTypes.func.isRequired,
|
||||||
|
toggleNotificationsBox: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
const { notifications, fetchNotifications } = this.props
|
||||||
|
if (!notifications) {
|
||||||
|
fetchNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickOutside = () => {
|
||||||
|
this.props.toggleNotificationsBox()
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { notifications } = this.props
|
||||||
|
return <div className='notificationsBox'>
|
||||||
|
<div className='notificationsBoxTriangle' />
|
||||||
|
<ul>
|
||||||
|
{!notifications && <li>loading...</li>}
|
||||||
|
<li>A notification</li>
|
||||||
|
<li>A notification</li>
|
||||||
|
<li>A notification</li>
|
||||||
|
<li>A notification</li>
|
||||||
|
</ul>
|
||||||
|
<a href='/notifications' className='notificationsBoxSeeAll'>See all</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default onClickOutsideAddon(NotificationBox)
|
|
@ -4,18 +4,14 @@ import PropTypes from 'prop-types'
|
||||||
class NotificationIcon extends Component {
|
class NotificationIcon extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
unreadNotificationsCount: PropTypes.number
|
unreadNotificationsCount: PropTypes.number,
|
||||||
}
|
toggleNotificationsBox: PropTypes.func
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
|
const { toggleNotificationsBox } = this.props
|
||||||
let linkClasses = 'notificationsIcon upperRightEl upperRightIcon '
|
let linkClasses = 'notificationsIcon upperRightEl upperRightIcon '
|
||||||
|
linkClasses += 'ignore-react-onclickoutside '
|
||||||
|
|
||||||
if (this.props.unreadNotificationsCount > 0) {
|
if (this.props.unreadNotificationsCount > 0) {
|
||||||
linkClasses += 'unread'
|
linkClasses += 'unread'
|
||||||
|
@ -24,14 +20,14 @@ class NotificationIcon extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className={linkClasses} href="/notifications" target="_blank">
|
<div className={linkClasses} onClick={toggleNotificationsBox}>
|
||||||
<div className="tooltipsUnder">
|
<div className="tooltipsUnder">
|
||||||
Notifications
|
Notifications
|
||||||
</div>
|
</div>
|
||||||
{this.props.unreadNotificationsCount === 0 ? null : (
|
{this.props.unreadNotificationsCount === 0 ? null : (
|
||||||
<div className="unread-notifications-dot"></div>
|
<div className="unread-notifications-dot"></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,31 +4,51 @@ import PropTypes from 'prop-types'
|
||||||
import AccountMenu from './AccountMenu'
|
import AccountMenu from './AccountMenu'
|
||||||
import LoginForm from './LoginForm'
|
import LoginForm from './LoginForm'
|
||||||
import NotificationIcon from './NotificationIcon'
|
import NotificationIcon from './NotificationIcon'
|
||||||
|
import NotificationBox from './NotificationBox'
|
||||||
|
|
||||||
class UpperRightUI extends Component {
|
class UpperRightUI extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
signInPage: PropTypes.bool,
|
signInPage: PropTypes.bool,
|
||||||
unreadNotificationsCount: PropTypes.number,
|
unreadNotificationsCount: PropTypes.number,
|
||||||
|
fetchNotifications: PropTypes.func,
|
||||||
|
notifications: PropTypes.array,
|
||||||
openInviteLightbox: PropTypes.func
|
openInviteLightbox: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {accountBoxOpen: false}
|
this.state = {
|
||||||
|
accountBoxOpen: false,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.setState({accountBoxOpen: false})
|
this.setState({
|
||||||
|
accountBoxOpen: false,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAccountBox = () => {
|
toggleAccountBox = () => {
|
||||||
this.setState({accountBoxOpen: !this.state.accountBoxOpen})
|
this.setState({
|
||||||
|
accountBoxOpen: !this.state.accountBoxOpen,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleNotificationsBox = () => {
|
||||||
|
this.setState({
|
||||||
|
notificationsBoxOpen: !this.state.notificationsBoxOpen,
|
||||||
|
accountBoxOpen: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { currentUser, signInPage, unreadNotificationsCount, openInviteLightbox } = this.props
|
const { currentUser, signInPage, unreadNotificationsCount,
|
||||||
const { accountBoxOpen } = this.state
|
notifications, fetchNotifications, openInviteLightbox } = this.props
|
||||||
|
const { accountBoxOpen, notificationsBoxOpen } = this.state
|
||||||
return <div className="upperRightUI">
|
return <div className="upperRightUI">
|
||||||
{currentUser && <a href="/maps/new" target="_blank" className="addMap upperRightEl upperRightIcon">
|
{currentUser && <a href="/maps/new" target="_blank" className="addMap upperRightEl upperRightIcon">
|
||||||
<div className="tooltipsUnder">
|
<div className="tooltipsUnder">
|
||||||
|
@ -36,7 +56,13 @@ class UpperRightUI extends Component {
|
||||||
</div>
|
</div>
|
||||||
</a>}
|
</a>}
|
||||||
{currentUser && <span id="notification_icon">
|
{currentUser && <span id="notification_icon">
|
||||||
<NotificationIcon unreadNotificationsCount={unreadNotificationsCount} />
|
<NotificationIcon
|
||||||
|
unreadNotificationsCount={unreadNotificationsCount}
|
||||||
|
toggleNotificationsBox={this.toggleNotificationsBox}/>
|
||||||
|
{notificationsBoxOpen && <NotificationBox
|
||||||
|
notifications={notifications}
|
||||||
|
fetchNotifications={fetchNotifications}
|
||||||
|
toggleNotificationsBox={this.toggleNotificationsBox}/>}
|
||||||
</span>}
|
</span>}
|
||||||
{!signInPage && <div className="sidebarAccount upperRightEl">
|
{!signInPage && <div className="sidebarAccount upperRightEl">
|
||||||
<div className="sidebarAccountIcon ignore-react-onclickoutside" onClick={this.toggleAccountBox}>
|
<div className="sidebarAccountIcon ignore-react-onclickoutside" onClick={this.toggleAccountBox}>
|
||||||
|
|
|
@ -11,6 +11,8 @@ class App extends Component {
|
||||||
children: PropTypes.object,
|
children: PropTypes.object,
|
||||||
toast: PropTypes.string,
|
toast: PropTypes.string,
|
||||||
unreadNotificationsCount: PropTypes.number,
|
unreadNotificationsCount: PropTypes.number,
|
||||||
|
notifications: PropTypes.array,
|
||||||
|
fetchNotifications: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
mobileTitle: PropTypes.string,
|
mobileTitle: PropTypes.string,
|
||||||
|
@ -38,7 +40,7 @@ class App extends Component {
|
||||||
const { children, toast, unreadNotificationsCount, openInviteLightbox,
|
const { children, toast, unreadNotificationsCount, openInviteLightbox,
|
||||||
mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location,
|
mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location,
|
||||||
map, userRequested, requestAnswered, requestApproved, serverData,
|
map, userRequested, requestAnswered, requestApproved, serverData,
|
||||||
onRequestAccess } = this.props
|
onRequestAccess, notifications, fetchNotifications } = this.props
|
||||||
const { pathname } = location || {}
|
const { pathname } = location || {}
|
||||||
// this fixes a bug that happens otherwise when you logout
|
// this fixes a bug that happens otherwise when you logout
|
||||||
const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null
|
const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null
|
||||||
|
@ -58,6 +60,8 @@ class App extends Component {
|
||||||
onRequestClick={onRequestAccess} />}
|
onRequestClick={onRequestAccess} />}
|
||||||
{!mobile && <UpperRightUI currentUser={currentUser}
|
{!mobile && <UpperRightUI currentUser={currentUser}
|
||||||
unreadNotificationsCount={unreadNotificationsCount}
|
unreadNotificationsCount={unreadNotificationsCount}
|
||||||
|
notifications={notifications}
|
||||||
|
fetchNotifications={fetchNotifications}
|
||||||
openInviteLightbox={openInviteLightbox}
|
openInviteLightbox={openInviteLightbox}
|
||||||
signInPage={pathname === '/login'} />}
|
signInPage={pathname === '/login'} />}
|
||||||
<Toast message={toast} />
|
<Toast message={toast} />
|
||||||
|
|
Loading…
Reference in a new issue