From 04298aca7d39d739e5d98a6fb7c353e66f4656ca Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 14 Mar 2017 16:30:02 -0600 Subject: [PATCH 1/6] change Stream > Story sometimes --- client/coral-admin/src/AppRouter.js | 4 +- .../coral-admin/src/components/ui/Drawer.js | 4 +- .../coral-admin/src/components/ui/Header.js | 4 +- .../Streams.css => Stories/Stories.css} | 0 .../Streams.js => Stories/Stories.js} | 6 +- .../src/containers/Streams/Stories.js | 187 ++++++++++++++++++ client/coral-admin/src/translations.json | 10 +- 7 files changed, 201 insertions(+), 14 deletions(-) rename client/coral-admin/src/containers/{Streams/Streams.css => Stories/Stories.css} (100%) rename client/coral-admin/src/containers/{Streams/Streams.js => Stories/Stories.js} (97%) create mode 100644 client/coral-admin/src/containers/Streams/Stories.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 38d06dcb1..a2990677f 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,7 +1,7 @@ import React from 'react'; import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router'; -import Streams from 'containers/Streams/Streams'; +import Stories from 'containers/Stories/Stories'; import Configure from 'containers/Configure/Configure'; import LayoutContainer from 'containers/LayoutContainer'; import InstallContainer from 'containers/Install/InstallContainer'; @@ -18,7 +18,7 @@ const routes = ( - + {/* Moderation Routes */} diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index 9af651f3b..5ac55045c 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -23,9 +23,9 @@ const CoralDrawer = ({handleLogout, restricted = false}) => ( {lang.t('configure.moderate')} - {lang.t('configure.streams')} + {lang.t('configure.stories')} ( - {lang.t('configure.streams')} + {lang.t('configure.stories')} { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Streams); +export default connect(mapStateToProps, mapDispatchToProps)(Stories); const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Streams/Stories.js b/client/coral-admin/src/containers/Streams/Stories.js new file mode 100644 index 000000000..4d2ad086a --- /dev/null +++ b/client/coral-admin/src/containers/Streams/Stories.js @@ -0,0 +1,187 @@ +import React, {Component} from 'react'; +import styles from './Stories.css'; +import {connect} from 'react-redux'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import {fetchAssets, updateAssetState} from '../../actions/assets'; +import translations from '../../translations.json'; +import {Link} from 'react-router'; + +import {Pager, Icon} from 'coral-ui'; +import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; +import EmptyCard from 'coral-admin/src/components/EmptyCard'; + +const limit = 25; + +class Stories extends Component { + + state = { + search: '', + sort: 'desc', + filter: 'all', + statusMenus: {}, + timer: null, + page: 0 + } + + componentDidMount () { + this.props.fetchAssets(0, limit, '', this.state.sortBy); + } + + onSettingChange = (setting) => (e) => { + let options = this.state; + this.setState({[setting]: e.target.value}); + options[setting] = e.target.value; + this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); + } + + onSearchChange = (e) => { + const search = e.target.value; + this.setState((prevState) => { + prevState.search = search; + clearTimeout(prevState.timer); + const fetchAssets = this.props.fetchAssets; + prevState.timer = setTimeout(() => { + fetchAssets(0, limit, search, this.state.sort, this.state.filter); + }, 350); + return prevState; + }); + } + + renderDate = (date) => { + const d = new Date(date); + return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; + } + + onStatusClick = (closeStream, id, statusMenuOpen) => () => { + if (statusMenuOpen) { + this.setState(prev => { + prev.statusMenus[id] = false; + return prev; + }); + this.props.updateAssetState(id, closeStream ? Date.now() : null) + .then(() => { + const {search, sort, filter, page} = this.state; + this.props.fetchAssets(page, limit, search, sort, filter); + }); + } else { + this.setState(prev => { + prev.statusMenus[id] = true; + return prev; + }); + } + } + + renderTitle = (title, {id}) => {title} + + renderStatus = (closedAt, {id}) => { + const closed = closedAt && new Date(closedAt).getTime() < Date.now(); + const statusMenuOpen = this.state.statusMenus[id]; + return
+
+ {!statusMenuOpen && } + {closed ? lang.t('streams.closed') : lang.t('streams.open')} +
+ { + statusMenuOpen && +
+ {!closed ? lang.t('streams.closed') : lang.t('streams.open')} +
+ } +
; + } + + onPageClick = (page) => { + this.setState({page}); + const {search, sort, filter} = this.state; + this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); + } + + render () { + const {search, sort, filter} = this.state; + const {assets} = this.props; + + const assetsIds = assets.ids.map((id) => assets.byId[id]); + + return ( +
+
+
+ + +
+
{lang.t('streams.filter-streams')}
+
{lang.t('streams.stream-status')}
+ + {lang.t('streams.all')} + {lang.t('streams.open')} + {lang.t('streams.closed')} + +
{lang.t('streams.sort-by')}
+ + {lang.t('streams.newest')} + {lang.t('streams.oldest')} + +
+ { + assetsIds.length + ?
+ + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + + + +
+ : {lang.t('streams.empty_result')} + } +
+ ); + } +} + +const mapStateToProps = ({assets}) => { + return { + assets: assets.toJS() + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + fetchAssets: (...args) => { + dispatch(fetchAssets.apply(this, args)); + }, + updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Stories); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 58788e546..91fa60d43 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -81,7 +81,7 @@ "moderate": "Moderate", "configure": "Configure", "community": "Community", - "streams": "Streams", + "stories": "Stories", "closed-comments-desc": "Write a message to be displayed when when your comment stream is closed and no longer accepting comments.", "closed-comments-label": "Write a message...", "hours": "Hours", @@ -138,9 +138,9 @@ "sort-by": "Sort By", "open": "Open", "closed": "Closed", - "article": "Article", + "article": "Story", "pubdate": "Publication Date", - "status": "Status" + "status": "Stream Status" } }, "es": { @@ -212,7 +212,7 @@ "moderate": "Moderar", "configure": "Configurar", "community": "Comunidad", - "streams": "Streams", + "stories": "Artículos", "closed-comments-desc": "Escribe un mensaje que será mostrado cuando los comentarios estén cerrados y no se acepten más comentarios.", "closed-comments-label": "Escribe un mensaje...", "never": "Nunca", @@ -259,7 +259,7 @@ "sort-by": "", "open": "", "closed": "", - "article": "", + "article": "artículo", "pubdate": "", "status": "" } From 3071b3783e92d49a5e70081e896dc555b36a9afb Mon Sep 17 00:00:00 2001 From: riley Date: Wed, 15 Mar 2017 10:15:47 -0600 Subject: [PATCH 2/6] move pre-mod links --- .../containers/Configure/ModerationSettings.js | 18 ++++++++++++++++++ .../src/containers/Configure/StreamSettings.js | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/ModerationSettings.js b/client/coral-admin/src/containers/Configure/ModerationSettings.js index da158b612..5b7286b98 100644 --- a/client/coral-admin/src/containers/Configure/ModerationSettings.js +++ b/client/coral-admin/src/containers/Configure/ModerationSettings.js @@ -16,6 +16,11 @@ const updateEmailConfirmation = (updateSettings, verify) => () => { updateSettings({requireEmailConfirmation: !verify}); }; +const updatePremodLinksEnable = (updateSettings, premodLinks) => () => { + const premodLinksEnable = !premodLinks; + updateSettings({premodLinksEnable}); +}; + const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => { // just putting this here for shorthand below @@ -50,6 +55,19 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {

+ +
+ +
+
+
{lang.t('configure.enable-premod-links')}
+

+ {lang.t('configure.enable-premod-links-text')} +

+
+
() => { updateSettings({infoBoxEnable}); }; -const updatePremodLinksEnable = (updateSettings, premodLinks) => () => { - const premodLinksEnable = !premodLinks; - updateSettings({premodLinksEnable}); -}; - const updateInfoBoxContent = (updateSettings) => (event) => { const infoBoxContent = event.target.value; updateSettings({infoBoxContent}); @@ -99,19 +94,6 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {

- -
- -
-
-
{lang.t('configure.enable-premod-links')}
-

- {lang.t('configure.enable-premod-links-text')} -

-
-
Date: Wed, 15 Mar 2017 11:16:13 -0600 Subject: [PATCH 3/6] correctly handle error when not logged in --- client/coral-admin/src/actions/auth.js | 20 ++++++++++++++------ client/coral-framework/actions/auth.js | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 389e8b4d2..dd2eaa7dc 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -5,9 +5,13 @@ import coralApi from 'coral-framework/helpers/response'; export const handleLogin = (email, password) => dispatch => { dispatch({type: actions.LOGIN_REQUEST}); return coralApi('/auth/local', {method: 'POST', body: {email, password}}) - .then(result => { - const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; - dispatch(checkLoginSuccess(result.user, isAdmin)); + .then(({user}) => { + if (!user) { + return dispatch(checkLoginFailure('not logged in')); + } + + const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + dispatch(checkLoginSuccess(user, isAdmin)); }) .catch(error => { dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); @@ -34,9 +38,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { dispatch(checkLoginRequest()); return coralApi('/auth') - .then(result => { - const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; - dispatch(checkLoginSuccess(result.user, isAdmin)); + .then(({user}) => { + if (!user) { + return dispatch(checkLoginFailure('not logged in')); + } + + const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + dispatch(checkLoginSuccess(user, isAdmin)); }) .catch(error => { console.error(error); diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index ba1952e29..9dedaa2f2 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -48,7 +48,7 @@ export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); return coralApi('/auth/local', {method: 'POST', body: formData}) .then(({user}) => { - const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + const isAdmin = !!user && !!user.roles.filter(i => i === 'ADMIN').length; dispatch(signInSuccess(user, isAdmin)); dispatch(hideSignInDialog()); }) From 9ddc3aa0b79592fafb30a771ef98b2d2a0d93a5e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 15 Mar 2017 14:13:18 -0600 Subject: [PATCH 4/6] move CountdownTimer to its own component --- .../src/components/CountdownTimer.js | 79 +++++++++++++++++++ .../src/containers/Dashboard/Dashboard.js | 71 +++-------------- 2 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 client/coral-admin/src/components/CountdownTimer.js diff --git a/client/coral-admin/src/components/CountdownTimer.js b/client/coral-admin/src/components/CountdownTimer.js new file mode 100644 index 000000000..3eaa6d305 --- /dev/null +++ b/client/coral-admin/src/components/CountdownTimer.js @@ -0,0 +1,79 @@ +import React, {PropTypes} from 'react'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-admin/src/translations'; +import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css'; +import {Icon} from 'coral-ui'; + +const lang = new I18n(translations); +const refreshIntervalSeconds = 60 * 5; + +class CountdownTimer extends React.Component { + + static propTypes = { + handleTimeout: PropTypes.func.isRequired + } + + constructor (props) { + super(props); + try { + if (window.localStorage.getItem('coral:dashboardNote') === null) { + window.localStorage.setItem('coral:dashboardNote', 'show'); + } + } catch (e) { + + // above will fail in Private Mode in some browsers. + } + + this.state = { + secondsUntilRefresh: refreshIntervalSeconds, + dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show' + }; + } + + componentWillMount () { + setInterval(() => { // the countdown timer + let nextCount = this.state.secondsUntilRefresh - 1; + if (nextCount < 0) { + nextCount = refreshIntervalSeconds; + this.props.handleTimeout(); + } + this.setState({secondsUntilRefresh: nextCount}); + }, 1000); + } + + formatTime = () => { + const minutes = Math.floor(this.state.secondsUntilRefresh / 60); + let seconds = (this.state.secondsUntilRefresh % 60).toString(); + if (seconds.length < 2) { + seconds = `0${seconds}`; + } + + return `${minutes}:${seconds}`; + } + + dismissNote = () => { + try { + window.localStorage.setItem('coral:dashboardNote', 'hide'); + } catch (e) { + + // when setItem fails in Safari Private mode + this.setState({dashboardNote: 'hide'}); + } + } + + render () { + const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' || + this.state.dashboardNote === 'hide'; // for Safari Incognito + return ( +

+ × + {lang.t('dashboard.next-update', this.formatTime())} {lang.t('dashboard.auto-update')} +

+ ); + } +} + +export default CountdownTimer; diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 602a48d7f..728d40c07 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -5,63 +5,15 @@ import {connect} from 'react-redux'; import {getMetrics} from 'coral-admin/src/graphql/queries'; import FlagWidget from './FlagWidget'; import ActivityWidget from './ActivityWidget'; +import CountdownTimer from 'coral-admin/src/components/CountdownTimer'; import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from 'coral-admin/src/translations'; -import {Spinner, Icon} from 'coral-ui'; -const lang = new I18n(translations); -const refreshIntervalSeconds = 60 * 5; +import {Spinner} from 'coral-ui'; class Dashboard extends React.Component { - constructor (props) { - super(props); - - try { - if (window.localStorage.getItem('coral:dashboardNote') === null) { - window.localStorage.setItem('coral:dashboardNote', 'show'); - } - } catch (e) { - - // above will fail in Private Mode in some browsers. - } - - this.state = { - secondsUntilRefresh: refreshIntervalSeconds, - dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show' - }; - } - - componentWillMount () { - setInterval(() => { // the countdown timer - let nextCount = this.state.secondsUntilRefresh - 1; - if (nextCount < 0) { - nextCount = refreshIntervalSeconds; - this.props.data.refetch(); - } - this.setState({secondsUntilRefresh: nextCount}); - }, 1000); - } - - dismissNote = () => { - try { - window.localStorage.setItem('coral:dashboardNote', 'hide'); - } catch (e) { - - // when setItem fails in Safari Private mode - this.setState({dashboardNote: 'hide'}); - } - } - - formatTime = () => { - const minutes = Math.floor(this.state.secondsUntilRefresh / 60); - let seconds = (this.state.secondsUntilRefresh % 60).toString(); - if (seconds.length < 2) { - seconds = `0${seconds}`; - } - - return `${minutes}:${seconds}`; + reloadData = () => { + this.props.data.refetch(); } render () { @@ -70,19 +22,16 @@ class Dashboard extends React.Component { return ; } + console.log('data', this.props.data); + const {data: {assetsByActivity, assetsByFlag}} = this.props; - const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' || - this.state.dashboardNote === 'hide'; // for Safari Incognito + + console.log('assetsByActivity', assetsByActivity); + console.log('assetsByFlag', assetsByActivity); return (
-

- × - {lang.t('dashboard.next-update', this.formatTime())} {lang.t('dashboard.auto-update')} -

+
From 378e855c4c02293ea5a8107048f439adee729d45 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 15 Mar 2017 14:20:27 -0600 Subject: [PATCH 5/6] remove logs --- client/coral-admin/src/containers/Dashboard/Dashboard.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 728d40c07..f5d0aa39b 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -22,13 +22,8 @@ class Dashboard extends React.Component { return ; } - console.log('data', this.props.data); - const {data: {assetsByActivity, assetsByFlag}} = this.props; - console.log('assetsByActivity', assetsByActivity); - console.log('assetsByFlag', assetsByActivity); - return (
From 1043a37eef07b697fd8cd27e530a8b7f6a2fff8e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 09:28:17 -0600 Subject: [PATCH 6/6] do a null check. --- client/coral-admin/src/containers/Dashboard/FlagWidget.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 468875d3d..17be2acbd 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -19,7 +19,11 @@ const FlagWidget = ({assets}) => { { assets.length ? assets.map(asset => { - const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); + let flagSummary = null; + if (asset.action_summaries) { + flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); + } + return (
Moderate