diff --git a/app/assets/stylesheets/notifications.scss.erb b/app/assets/stylesheets/notifications.scss.erb index 2f750f90..95524caa 100644 --- a/app/assets/stylesheets/notifications.scss.erb +++ b/app/assets/stylesheets/notifications.scss.erb @@ -13,6 +13,45 @@ $unread_notifications_dot_size: 8px; .notificationsIcon { 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 { @@ -66,12 +105,12 @@ $unread_notifications_dot_size: 8px; & > a { float: left; - width: 85%; + width: 85%; box-sizing: border-box; padding-right: 10px; } - .notification-actor { + .notification-actor { float: left; img { @@ -97,7 +136,7 @@ $unread_notifications_dot_size: 8px; display: inline-block; margin: 5px 0; } - } + } .notification-date { position: absolute; @@ -129,7 +168,7 @@ $unread_notifications_dot_size: 8px; } - + .notificationPage { .thirty-two-avatar { @@ -139,14 +178,14 @@ $unread_notifications_dot_size: 8px; border-radius: 16px; vertical-align: middle; } - + .button { line-height: 32px; - + img { margin-top: 8px; } - + &.decline { background: #DB5D5D; &:hover { @@ -154,7 +193,7 @@ $unread_notifications_dot_size: 8px; } } } - + .notification-body { p, div { margin: 1em auto; diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 210c0b43..6d75d24e 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -10,10 +10,15 @@ class NotificationsController < ApplicationController respond_to do |format| format.html format.json do - render json: @notifications.map do |notification| + notifications = @notifications.map do |notification| 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 + render json: notifications end end end diff --git a/frontend/src/Metamaps/GlobalUI/Notifications.js b/frontend/src/Metamaps/GlobalUI/Notifications.js new file mode 100644 index 00000000..1ca9e36a --- /dev/null +++ b/frontend/src/Metamaps/GlobalUI/Notifications.js @@ -0,0 +1,14 @@ +const Notifications = { + notifications: null, + fetch: render => { + $.ajax({ + url: '/notifications.json', + success: function(data) { + Notifications.notifications = data + render() + } + }) + } +} + +export default Notifications diff --git a/frontend/src/Metamaps/GlobalUI/ReactApp.js b/frontend/src/Metamaps/GlobalUI/ReactApp.js index 5cfbf255..8cc85840 100644 --- a/frontend/src/Metamaps/GlobalUI/ReactApp.js +++ b/frontend/src/Metamaps/GlobalUI/ReactApp.js @@ -8,6 +8,7 @@ import apply from 'async/apply' import { notifyUser } from './index.js' import ImportDialog from './ImportDialog' +import Notifications from './Notifications' import Active from '../Active' import DataModel from '../DataModel' import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views' @@ -107,7 +108,9 @@ const ReactApp = { mobileTitleWidth: self.mobileTitleWidth, mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e), openInviteLightbox: () => self.openLightbox('invite'), - serverData: self.serverData + serverData: self.serverData, + notifications: Notifications.notifications, + fetchNotifications: apply(Notifications.fetch, ReactApp.render) }, self.getMapProps(), self.getTopicProps(), diff --git a/frontend/src/components/App/NotificationBox.js b/frontend/src/components/App/NotificationBox.js new file mode 100644 index 00000000..675a2a3b --- /dev/null +++ b/frontend/src/components/App/NotificationBox.js @@ -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
+
+ + See all +
+ } +} + +export default onClickOutsideAddon(NotificationBox) diff --git a/frontend/src/components/App/NotificationIcon.js b/frontend/src/components/App/NotificationIcon.js index 36b86b72..b265cec0 100644 --- a/frontend/src/components/App/NotificationIcon.js +++ b/frontend/src/components/App/NotificationIcon.js @@ -4,18 +4,14 @@ import PropTypes from 'prop-types' class NotificationIcon extends Component { static propTypes = { - unreadNotificationsCount: PropTypes.number - } - - constructor(props) { - super(props) - - this.state = { - } + unreadNotificationsCount: PropTypes.number, + toggleNotificationsBox: PropTypes.func } render = () => { + const { toggleNotificationsBox } = this.props let linkClasses = 'notificationsIcon upperRightEl upperRightIcon ' + linkClasses += 'ignore-react-onclickoutside ' if (this.props.unreadNotificationsCount > 0) { linkClasses += 'unread' @@ -24,14 +20,14 @@ class NotificationIcon extends Component { } return ( - +
Notifications
{this.props.unreadNotificationsCount === 0 ? null : (
)} -
+
) } diff --git a/frontend/src/components/App/UpperRightUI.js b/frontend/src/components/App/UpperRightUI.js index d1e53652..a1fa0d32 100644 --- a/frontend/src/components/App/UpperRightUI.js +++ b/frontend/src/components/App/UpperRightUI.js @@ -4,31 +4,51 @@ import PropTypes from 'prop-types' import AccountMenu from './AccountMenu' import LoginForm from './LoginForm' import NotificationIcon from './NotificationIcon' +import NotificationBox from './NotificationBox' class UpperRightUI extends Component { static propTypes = { currentUser: PropTypes.object, signInPage: PropTypes.bool, unreadNotificationsCount: PropTypes.number, + fetchNotifications: PropTypes.func, + notifications: PropTypes.array, openInviteLightbox: PropTypes.func } constructor(props) { super(props) - this.state = {accountBoxOpen: false} + this.state = { + accountBoxOpen: false, + notificationsBoxOpen: false + } } reset = () => { - this.setState({accountBoxOpen: false}) + this.setState({ + accountBoxOpen: false, + notificationsBoxOpen: false + }) } 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 () { - const { currentUser, signInPage, unreadNotificationsCount, openInviteLightbox } = this.props - const { accountBoxOpen } = this.state + const { currentUser, signInPage, unreadNotificationsCount, + notifications, fetchNotifications, openInviteLightbox } = this.props + const { accountBoxOpen, notificationsBoxOpen } = this.state return
{currentUser &&
@@ -36,7 +56,13 @@ class UpperRightUI extends Component {
} {currentUser && - + + {notificationsBoxOpen && } } {!signInPage &&
diff --git a/frontend/src/components/App/index.js b/frontend/src/components/App/index.js index ed24e9d1..207da1cd 100644 --- a/frontend/src/components/App/index.js +++ b/frontend/src/components/App/index.js @@ -11,6 +11,8 @@ class App extends Component { children: PropTypes.object, toast: PropTypes.string, unreadNotificationsCount: PropTypes.number, + notifications: PropTypes.array, + fetchNotifications: PropTypes.func, location: PropTypes.object, mobile: PropTypes.bool, mobileTitle: PropTypes.string, @@ -38,7 +40,7 @@ class App extends Component { const { children, toast, unreadNotificationsCount, openInviteLightbox, mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location, map, userRequested, requestAnswered, requestApproved, serverData, - onRequestAccess } = this.props + onRequestAccess, notifications, fetchNotifications } = this.props const { pathname } = location || {} // this fixes a bug that happens otherwise when you logout const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null @@ -58,6 +60,8 @@ class App extends Component { onRequestClick={onRequestAccess} />} {!mobile && }