diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js index f897c1bae..095ef7aac 100644 --- a/client/coral-admin/src/reducers/auth.js +++ b/client/coral-admin/src/reducers/auth.js @@ -24,10 +24,7 @@ export default function auth (state = initialState, action) { .set('isAdmin', action.isAdmin) .set('user', action.user); case actions.LOGOUT_SUCCESS: - return state - .set('loggedIn', false) - .set('user', null) - .set('isAdmin', false); + return initialState; default : return state; } diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 95a6f63f3..26142a311 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -1,11 +1,14 @@ import React, {Component, PropTypes} from 'react'; +import Pym from 'pym.js'; +import {connect} from 'react-redux'; + import { itemActions, Notification, notificationActions, authActions, } from '../../coral-framework'; -import {connect} from 'react-redux'; + import CommentBox from '../../coral-plugin-commentbox/CommentBox'; import InfoBox from '../../coral-plugin-infobox/InfoBox'; import Content from '../../coral-plugin-commentcontent/CommentContent'; @@ -13,44 +16,20 @@ import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from '../../coral-plugin-comment-count/CommentCount'; import AuthorName from '../../coral-plugin-author-name/AuthorName'; import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; -import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; import LikeButton from '../../coral-plugin-likes/LikeButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; import UserBox from '../../coral-sign-in/components/UserBox'; -import {TabBar, Tab, TabContent} from '../../coral-ui'; +import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; +import RestrictedContent from '../../coral-framework/components/RestrictedContent'; +import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; const {logout} = authActions; -const mapStateToProps = (state) => { - return { - config: state.config.toJS(), - items: state.items.toJS(), - notification: state.notification.toJS(), - auth: state.auth.toJS() - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - addItem: (item, itemType) => dispatch(addItem(item, itemType)), - updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), - postItem: (data, type, id) => dispatch(postItem(data, type, id)), - getStream: (rootId) => dispatch(getStream(rootId)), - addNotification: (type, text) => dispatch(addNotification(type, text)), - clearNotification: () => dispatch(clearNotification()), - postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)), - deleteAction: (item, action, user, itemType) => { - return dispatch(deleteAction(item, action, user, itemType)); - }, - appendItemArray: (item, property, value, addToFront, itemType) => - dispatch(appendItemArray(item, property, value, addToFront, itemType)), - logout: () => dispatch(logout()), -}); - class CommentStream extends Component { constructor (props) { @@ -78,47 +57,22 @@ class CommentStream extends Component { componentDidMount () { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards - this.pym = new Pym.Child({polling: 100}); + const pym = new Pym.Child({polling: 100}); - const path = this.pym.parentUrl.split('#')[0]; - - this.props.getStream(path || window.location); - this.path = path; - - this.pym.sendMessage('childReady'); - - this.pym.onMessage('DOMContentLoaded', hash => { - // the comment ids can start with numbers, which is invalid for DOM id attributes - const commentId = hash.replace('#', 'c_'); - let count = 0; - const interval = setInterval(() => { - if (document.getElementById(commentId)) { - window.clearInterval(interval); - this.pym.scrollParentToChildEl(commentId); - } - - if (++count > 100) { // ~10 seconds - // give up waiting for the comments to load. - // it would be weird for the page to jump after that long. - window.clearInterval(interval); - } - }, 100); - }); + if (/https?\:\/\/([^?]+)/.test(pym.parentUrl)) { + this.props.getStream(pym.parentUrl); + } else { + this.props.getStream(window.location); + } } render () { if (Object.keys(this.props.items).length === 0) { - // Loading mock asset + // Loading mock asset this.props.postItem({ comments: [], url: 'http://coralproject.net' }, 'asset', 'assetTest'); - - // Loading mock user - //this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989') - // .then((id) => { - // this.props.setLoggedInUser(id); - // }); } // TODO: Replace teststream id with id from params @@ -132,68 +86,69 @@ class CommentStream extends Component { return
{ rootItem - ?
+ ?
Settings - - -
- - {loggedIn && } - - {!loggedIn && } -
- { - rootItem.comments && rootItem.comments.map((commentId) => { - const comment = comments[commentId]; - return
-
- - - -
- - -
-
- + {loggedIn && } + {/* Add to the restricted param a boolean if the user is suspended*/} + }> + +
+ + + {!loggedIn && } +
+ { + rootItem.comments && rootItem.comments.map((commentId) => { + const comment = comments[commentId]; + return
+
+ + + +
+ + +
+
+ -
+
- - -
-
- - -
- + +
+
+ + +
+
; }) - } -
; - }) - } - - - - - + } +
; + }) + } + + + + {!loggedIn && } + + +
- : 'Loading' + : + } ; } } +const mapStateToProps = state => ({ + config: state.config.toJS(), + items: state.items.toJS(), + notification: state.notification.toJS(), + auth: state.auth.toJS(), + userData: state.user.toJS() +}); + +const mapDispatchToProps = (dispatch) => ({ + addItem: (item, itemType) => dispatch(addItem(item, itemType)), + updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), + postItem: (data, type, id) => dispatch(postItem(data, type, id)), + getStream: (rootId) => dispatch(getStream(rootId)), + addNotification: (type, text) => dispatch(addNotification(type, text)), + clearNotification: () => dispatch(clearNotification()), + postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)), + deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), + appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), + handleSignInDialog: () => dispatch(authActions.showSignInDialog()), + logout: () => dispatch(logout()), +}); + export default connect(mapStateToProps, mapDispatchToProps)(CommentStream); diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 12bdfacae..b6fe672cc 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -84,6 +84,7 @@ hr { .coral-plugin-commentbox-textarea { flex: 1; padding: 5px; + min-height: 100px; } .coral-plugin-commentbox-button-container { @@ -110,6 +111,7 @@ hr { /* Comment styles */ .comment { + position: relative; margin-bottom: 10px; position: relative; } diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js new file mode 100644 index 000000000..72075e6db --- /dev/null +++ b/client/coral-framework/actions/user.js @@ -0,0 +1,21 @@ +import * as actions from '../constants/user'; +import {addNotification} from '../actions/notification'; +import coralApi from '../helpers/response'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from './../translations'; +const lang = new I18n(translations); + +const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST}); +const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings}); +const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error}); + +export const saveBio = (user_id, formData) => dispatch => { + dispatch(saveBioRequest()); + coralApi(`/user/${user_id}/bio`, {method: 'PUT', body: formData}) + .then(({settings}) => { + dispatch(addNotification('success', lang.t('successBioUpdate'))); + dispatch(saveBioSuccess(settings)); + }) + .catch(error => dispatch(saveBioFailure(error))); +}; diff --git a/client/coral-framework/components/RestrictedContent.css b/client/coral-framework/components/RestrictedContent.css new file mode 100644 index 000000000..47ced7b0b --- /dev/null +++ b/client/coral-framework/components/RestrictedContent.css @@ -0,0 +1,4 @@ +.message { + background: #D8D8D8; + padding: 25px; +} diff --git a/client/coral-framework/components/RestrictedContent.js b/client/coral-framework/components/RestrictedContent.js new file mode 100644 index 000000000..32aaf632d --- /dev/null +++ b/client/coral-framework/components/RestrictedContent.js @@ -0,0 +1,20 @@ +import React from 'react'; +import styles from './RestrictedContent.css'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-framework/translations.json'; +const lang = new I18n(translations); + +export default ({children, restricted, message = lang.t('contentNotAvailable'), restrictedComp}) => { + if (restricted) { + return restrictedComp ? restrictedComp : messageBox(message); + } else { + return ( +
+ {children} +
+ ); + } +}; + +const messageBox = (message) =>
{message}
; diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js new file mode 100644 index 000000000..5a23b77a2 --- /dev/null +++ b/client/coral-framework/components/SuspendedAccount.js @@ -0,0 +1,8 @@ +import React from 'react'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-framework/translations.json'; +const lang = new I18n(translations); + +export default () => ( + {lang.t('suspendedAccountMsg')} +); diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js new file mode 100644 index 000000000..0c316d48a --- /dev/null +++ b/client/coral-framework/constants/user.js @@ -0,0 +1,3 @@ +export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST'; +export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS'; +export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index a445fc663..a4803fa96 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -13,6 +13,11 @@ const initialState = Map({ successSignUp: false }); +const purge = user => { + const {settings, profiles, ...userData} = user; // eslint-disable-line + return userData; +}; + export default function auth (state = initialState, action) { switch (action.type) { case actions.SHOW_SIGNIN_DIALOG : @@ -44,11 +49,11 @@ export default function auth (state = initialState, action) { case actions.CHECK_LOGIN_SUCCESS: return state .set('loggedIn', true) - .set('user', action.user); + .set('user', purge(action.user)); case actions.FETCH_SIGNIN_SUCCESS: return state .set('loggedIn', true) - .set('user', action.user); + .set('user', purge(action.user)); case actions.FETCH_SIGNIN_FAILURE: return state .set('isLoading', false) @@ -56,7 +61,7 @@ export default function auth (state = initialState, action) { .set('user', null); case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS: return state - .set('user', action.user) + .set('user', purge(action.user)) .set('loggedIn', true); case actions.FETCH_SIGNIN_FACEBOOK_FAILURE: return state diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index 90eaa7cb7..a2f439028 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -5,6 +5,7 @@ import config from './config'; import items from './items'; import notification from './notification'; import auth from './auth'; +import user from './user'; /** * Expose the combined main reducer @@ -15,4 +16,5 @@ export default combineReducers({ items, notification, auth, + user }); diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js new file mode 100644 index 000000000..11b57fc15 --- /dev/null +++ b/client/coral-framework/reducers/user.js @@ -0,0 +1,36 @@ +import {Map} from 'immutable'; +import * as authActions from '../constants/auth'; +import * as actions from '../constants/user'; + +const initialState = Map({ + displayName: '', + profiles: [], + settings: {} +}); + +const purge = user => { + const {_id, created_at, updated_at, __v, roles, ...userData} = user; // eslint-disable-line + return userData; +}; + +export default function user (state = initialState, action) { + switch (action.type) { + case authActions.CHECK_LOGIN_SUCCESS: + return state.merge(Map(purge(action.user))); + case authActions.CHECK_LOGIN_FAILURE: + return initialState; + case authActions.FETCH_SIGNIN_SUCCESS: + return state.merge(Map(purge(action.user))); + case authActions.FETCH_SIGNIN_FAILURE: + return initialState; + case authActions.FETCH_SIGNIN_FACEBOOK_SUCCESS: + return state.merge(Map(purge(action.user))); + case authActions.FETCH_SIGNIN_FACEBOOK_FAILURE: + return initialState; + case actions.SAVE_BIO_SUCCESS: + return state + .set('settings', action.settings); + default : + return state; + } +} diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 4abd1ed03..ab65ba565 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -1,5 +1,8 @@ { "en": { + "successBioUpdate": "Your Bio has been updated", + "contentNotAvailable": "This content is not available", + "suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", "error": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", @@ -10,6 +13,9 @@ } }, "es": { + "successBioUpdate": "Tu bio fue actualizada", + "contentNotAvailable": "El contenido no se encuentra disponible", + "suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", "error": { "email": "No es un email válido", "password": "La contraseña debe tener por lo menos 8 caracteres", @@ -19,4 +25,4 @@ "emailInUse": "Email address already in use" } } -} \ No newline at end of file +} diff --git a/client/coral-plugin-author-name/AuthorName.js b/client/coral-plugin-author-name/AuthorName.js index aef819468..56b868726 100644 --- a/client/coral-plugin-author-name/AuthorName.js +++ b/client/coral-plugin-author-name/AuthorName.js @@ -1,9 +1,43 @@ -import React from 'react'; +import React, {Component} from 'react'; +import {Tooltip} from 'coral-ui'; const packagename = 'coral-plugin-author-name'; -const AuthorName = ({author}) => -
- {author && author.displayName} -
; +export default class AuthorName extends Component { + constructor (props) { + super(props); -export default AuthorName; + this.state = { + showTooltip: false + }; + + this.handleMouseOver = this.handleMouseOver.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); + } + + handleMouseOver () { + this.setState({ + showTooltip: true + }); + } + + handleMouseLeave () { + this.setState({ + showTooltip: false + }); + } + + render () { + const {author} = this.props; + const {showTooltip} = this.state; + return ( +
+ {author && author.displayName} + { showTooltip && {author.settings.bio}} +
+ ); + } +} diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 79641d856..9ae524652 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -1,6 +1,7 @@ import React, {Component, PropTypes} from 'react'; import {I18n} from '../coral-framework'; import translations from './translations.json'; +import {Button} from 'coral-ui'; const name = 'coral-plugin-commentbox'; @@ -75,12 +76,11 @@ class CommentBox extends Component {
{ author && ( - + ) }
diff --git a/client/coral-settings/components/Bio.js b/client/coral-settings/components/Bio.js index b82587322..cd1347781 100644 --- a/client/coral-settings/components/Bio.js +++ b/client/coral-settings/components/Bio.js @@ -2,15 +2,17 @@ import React from 'react'; import styles from './Bio.css'; import {Button} from '../../coral-ui'; -export default () => ( +export default ({bio, handleSave, handleInput, handleCancel}) => (

Bio

Tell the community about yourself

-