diff --git a/src/components/HOCs/WithSystemNotices/WithSystemNotices.js b/src/components/HOCs/WithSystemNotices/WithSystemNotices.js new file mode 100644 index 000000000..7b178098a --- /dev/null +++ b/src/components/HOCs/WithSystemNotices/WithSystemNotices.js @@ -0,0 +1,94 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { fetchActiveSystemNotices } + from '../../../services/SystemNotices/SystemNotices' + +const ACKNOWLEDGED_SETTING = 'acknowledgedNotices' + +/** + * WithSystemNotices provides the WrappedComponent with an array of active, + * unacknowledged system notices along with methods for acknowledging them + * + * @author [Neil Rotstan](https://github.com/nrotstan) + */ +export const WithSystemNotices = function(WrappedComponent) { + class _WithSystemNotices extends Component { + state = { + systemNotices: null, + unacknowledgedNotices: [], + } + + async componentDidMount() { + if (!this.state.systemNotices) { + const activeNotices = await fetchActiveSystemNotices() + + this.setState({systemNotices: activeNotices}) + } + } + + /** + * Retrieves all acknowledged notices from the user's app settings + * + * @private + */ + allAcknowledgedNotices = () => { + if (!this.props.user) { + return [] + } + + return this.props.getUserAppSetting(this.props.user, ACKNOWLEDGED_SETTING) || [] + } + + /** + * Retrieves array of notices that have not yet been acknowledged by the + * user + * + * @private + */ + unacknowledgedNotices = () => { + if (!this.state.systemNotices) { + return [] + } + + const acknowledged = this.allAcknowledgedNotices() + return this.state.systemNotices.filter( + notice => acknowledged.indexOf(notice.uuid) === -1 + ) + } + + /** + * Acknowledges the given notice + */ + acknowledgeNotice = async (notice) => { + if (!this.props.user || !notice.uuid) { + return + } + + const updatedAcknowledgements = this.allAcknowledgedNotices().slice() + updatedAcknowledgements.push(notice.uuid) + await this.props.updateUserAppSetting(this.props.user.id, { + [ACKNOWLEDGED_SETTING]: updatedAcknowledgements, + }) + } + + render() { + const remainingNotices = this.unacknowledgedNotices(this.state.systemNotices) + return this.acknowledgeNotice(notice)} + /> + } + } + + _WithSystemNotices.propTypes = { + user: PropTypes.object, + getUserAppSetting: PropTypes.func.isRequired, + updateUserAppSetting: PropTypes.func.isRequired, + } + + return _WithSystemNotices +} + +export default WrappedComponent => WithSystemNotices(WrappedComponent) diff --git a/src/components/Sprites/Sprites.js b/src/components/Sprites/Sprites.js index a939c3297..305b293df 100644 --- a/src/components/Sprites/Sprites.js +++ b/src/components/Sprites/Sprites.js @@ -11,7 +11,9 @@ export default function() { - + + + diff --git a/src/components/SystemNotices/SystemNotices.js b/src/components/SystemNotices/SystemNotices.js new file mode 100644 index 000000000..03c14ba38 --- /dev/null +++ b/src/components/SystemNotices/SystemNotices.js @@ -0,0 +1,44 @@ +import React from 'react' +import WithCurrentUser from '../HOCs/WithCurrentUser/WithCurrentUser' +import WithSystemNotices from '../HOCs/WithSystemNotices/WithSystemNotices' +import MarkdownContent from '../MarkdownContent/MarkdownContent' +import SvgSymbol from '../SvgSymbol/SvgSymbol' + +const SystemNotices = function(props) { + if (!props.newSystemNotices || props.newSystemNotices.length === 0) { + return null + } + + const notices = props.newSystemNotices.map(notice => ( +
  • + + + + + + props.acknowledgeNotice(notice)} + /> +
  • + )) + + return ( +
      + {notices} +
    + ) +} + +export default WithCurrentUser(WithSystemNotices(SystemNotices)) diff --git a/src/services/SystemNotices/SystemNotices.js b/src/services/SystemNotices/SystemNotices.js new file mode 100644 index 000000000..264675e17 --- /dev/null +++ b/src/services/SystemNotices/SystemNotices.js @@ -0,0 +1,57 @@ +import _isEmpty from 'lodash/isEmpty' +import _isArray from 'lodash/isArray' +import parse from 'date-fns/parse' +import isFuture from 'date-fns/is_future' + +const NOTICES_URL = process.env.REACT_APP_SYSTEM_NOTICES_URL + +/** + * Fetches system notices from the configured URL. Reponse is expected to be a + * JSON object with a top-level `notices` field containing an array of notice + * objects, each of which should contain a `message` field, ISO8601 formatted + * `expirationTimestamp` field, and a `uuid` field that uniquely identifies the + * notice. Any other top-level fields in the response object are ignored + * + * Only notices that have not yet expired are returned + * + * Example JSON: + * ``` + * { + * "notices": [ + * { + * "message": "A first notice. Maintenance is planned.", + * "expirationTimestamp": "2019-08-01T17:00:00Z", + * "uuid": "b98da355-a5e9-44b4-8a20-a5034d704de5" + * }, + * { + * "message": "A second notice. Important things are happening", + * "expirationTimestamp": "2019-08-04T15:00:00Z", + * "uuid": "94aef98e-bf9f-46a6-a860-85e62498ae3d" + * } + * ] + * } + * ``` + */ +export const fetchActiveSystemNotices = async function() { + if (_isEmpty(NOTICES_URL)) { + return [] + } + + const response = await fetch(NOTICES_URL) + if (response.ok) { + const systemNotices = await response.json() + if (!systemNotices || !_isArray(systemNotices.notices)) { + return [] + } + + return systemNotices.notices.map(notice => { + // add Date instance for expiration timestamp + notice.expirationDate = parse(notice.expirationTimestamp) + return notice + }).filter(notice => isFuture(notice.expirationDate)) + } + else { + // Allow server admin to delete file when not in use + return [] + } +} diff --git a/src/styles/utilities/typography.css b/src/styles/utilities/typography.css index 7610921b0..188f62290 100644 --- a/src/styles/utilities/typography.css +++ b/src/styles/utilities/typography.css @@ -136,4 +136,8 @@ h4, } } } + + &--base { + @apply mr-text-base; + } } diff --git a/src/tailwind.js b/src/tailwind.js index 0438d6a28..75baa3bab 100644 --- a/src/tailwind.js +++ b/src/tailwind.js @@ -830,6 +830,7 @@ module.exports = { 'green-lighter': colors['green-lighter'], grey: colors["grey"], 'grey-light': colors['grey-light'], + 'red-light': colors['red-light'], twitter: colors['twitter'], }, From c54b80efd28bb1242901365016c185f926ec4e16 Mon Sep 17 00:00:00 2001 From: Neil Rotstan Date: Mon, 5 Aug 2019 12:06:20 -0700 Subject: [PATCH 5/7] Don't offer notice dismissal to logged-out users --- src/components/SystemNotices/SystemNotices.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/SystemNotices/SystemNotices.js b/src/components/SystemNotices/SystemNotices.js index 03c14ba38..2a6849c99 100644 --- a/src/components/SystemNotices/SystemNotices.js +++ b/src/components/SystemNotices/SystemNotices.js @@ -23,12 +23,14 @@ const SystemNotices = function(props) { - props.acknowledgeNotice(notice)} - /> + {props.user && props.user.isLoggedIn && + props.acknowledgeNotice(notice)} + /> + } )) From 3c0337465f7edb662f9865d521fee073597a16f5 Mon Sep 17 00:00:00 2001 From: Neil Rotstan Date: Thu, 8 Aug 2019 14:29:48 -0700 Subject: [PATCH 6/7] Bump to v3.3.4 --- CHANGELOG.md | 9 ++++++++- package.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d041d3d..7609b8cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,14 @@ The format is based on This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [v3.3.3] - 2019-07-24 +## [v3.3.4] - 2019-08-08 +### Added +- Support for system notices (e.g. notices of upcoming maintenance) +- Color-coded usernames in various tables +- Bulk task-status change for challenge managers + + +## [v3.3.3] - 2019-07-25 ### Added - MapRoulette-specific tagging support for tasks ("MR Tags") - Option to select next nearby task to work on from map of nearby tasks diff --git a/package.json b/package.json index 1eda224b8..995faec42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maproulette3", - "version": "3.3.3", + "version": "3.3.4", "private": true, "dependencies": { "@mapbox/geojsonhint": "^2.0.1", From 6ed205a2b715debfb60084ebacd15197da2a61d8 Mon Sep 17 00:00:00 2001 From: Neil Rotstan Date: Thu, 8 Aug 2019 14:37:42 -0700 Subject: [PATCH 7/7] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7609b8cff..3c5e0b00a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ This project adheres to - Color-coded usernames in various tables - Bulk task-status change for challenge managers +### Fixed +- Malfunctioning review-table date picker in Safari + ## [v3.3.3] - 2019-07-25 ### Added