Merge branch 'master' of https://github.com/coralproject/talk into stream-e2e

This commit is contained in:
David Jay
2016-11-22 15:04:36 -05:00
64 changed files with 1502 additions and 4209 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ import CommunityContainer from 'containers/Community/CommunityContainer';
import LayoutContainer from 'containers/LayoutContainer';
const routes = (
<Route path='admin' component={LayoutContainer}>
<Route path='/admin' component={LayoutContainer}>
<IndexRoute component={ModerationQueue} />
<Route path='embed' component={CommentStream} />
<Route path='community' component={CommunityContainer} />
+19
View File
@@ -0,0 +1,19 @@
import * as actions from '../constants/auth';
import {base, handleResp, getInit} from '../helpers/response';
// Check Login
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin});
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
fetch(`${base}/auth`, getInit('GET'))
.then(handleResp)
.then(user => {
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
};
+7 -7
View File
@@ -1,11 +1,11 @@
import React from 'react';
import {Button, Icon} from 'react-mdl';
import timeago from 'timeago.js';
import styles from './CommentList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import Linkify from 'react-linkify';
import {FabButton} from 'coral-ui';
const linkify = new Linkify();
@@ -14,7 +14,7 @@ export default props => {
const links = linkify.getMatches(props.comment.get('body'));
return (
<li tabindex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<li tabIndex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<i className={`material-icons ${styles.avatar}`}>person</i>
@@ -26,12 +26,12 @@ export default props => {
{links ?
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
<div className={styles.actions}>
{props.actions.map(action => canShowAction(action, props.comment) ? (
<Button className={styles.actionButton}
{props.actions.map((action, i) => canShowAction(action, props.comment) ? (
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
cStyle={action}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
fab colored>
<Icon name={props.actionsMap[action].icon} />
</Button>
/>
) : null)}
</div>
</div>
@@ -112,15 +112,15 @@ export default class CommentList extends React.Component {
}
render () {
const {singleView, commentIds, comments, hideActive} = this.props;
const {singleView, commentIds, comments, hideActive, key} = this.props;
const {active} = this.state;
return (
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}>
{commentIds.map((commentId, index) => (
<Comment comment={comments.get(commentId)}
ref={el => { if (el && commentId === active) { this._active = el; } }}
key={`${index }comment`}
key={index}
index={index}
onClickAction={this.onClickAction}
actions={this.props.actions}
@@ -1,27 +0,0 @@
import React from 'react';
import {Layout, Navigation, Drawer, Header} from 'react-mdl';
import {Link} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
// App header. If we add a navbar it should be here
export default (props) => (
<Layout fixedDrawer>
<Header title='Talk'>
<Navigation>
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('Configure')}</Link>
</Navigation>
</Header>
<Drawer>
<Navigation>
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('configure.Configure')}</Link>
</Navigation>
</Drawer>
{props.children}
</Layout>
);
const lang = new I18n(translations);
@@ -0,0 +1,12 @@
.layout {
max-width: 800px;
margin: 0 auto;
}
.layout h1 {
font-size: 40px;
}
.layout img {
width: 100%;
}
@@ -0,0 +1,13 @@
import React from 'react';
import {Layout} from 'react-mdl';
import styles from './NotFound.css';
export const NotFound = () => (
<Layout fixedDrawer>
<div className={styles.layout} >
<h1>Page Not Found</h1>
<p>The communicorn feels your pain.</p>
<img src="https://coralproject.net/images/communicorn.jpg" alt="Communicorn"/>
</div>
</Layout>
);
@@ -0,0 +1,13 @@
import React from 'react';
import {Layout} from 'react-mdl';
import styles from './NotFound.css';
export const PermissionRequired = () => (
<Layout fixedDrawer>
<div className={styles.layout} >
<h1>Permission Required</h1>
<p>Were sorry, but you dont have access to that page.</p>
<img src="https://coralproject.net/images/communicorn.jpg" alt="Communicorn"/>
</div>
</Layout>
);
@@ -0,0 +1,21 @@
.header {
background: #505050;
overflow: hidden;
}
.header > div {
position: relative;
padding: 0;
width: 1170px;
margin: 0 auto;
}
.active {
background: #232323;
}
.version {
position: absolute;
right: 0;
width: 50px;
}
+10 -8
View File
@@ -1,20 +1,22 @@
import React from 'react';
import {Navigation, Header} from 'react-mdl';
import {Link} from 'react-router';
import {Link, IndexLink} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Logo} from './Logo';
export default () => (
<Header title='Talk'>
<Header className={styles.header}>
<Logo />
<Navigation>
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
<span>
{`v${process.env.VERSION}`}
</span>
<IndexLink className={styles.navLink} to="/admin" activeClassName={styles.active}>{lang.t('configure.moderate')}</IndexLink>
<Link className={styles.navLink} to="/admin/community" activeClassName={styles.active}>{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure" activeClassName={styles.active}>{lang.t('configure.configure')}</Link>
</Navigation>
<div className={styles.version}>
{`v${process.env.VERSION}`}
</div>
</Header>
);
@@ -0,0 +1,18 @@
.logo h1 {
color: #272727;
font-size: 20px;
padding: 0 30px;
}
.logo span {
display: inline-block;
margin-left: 10px;
font-size: 18px;
vertical-align: middle;
}
.logo {
background: #E5E5E5;
}
@@ -0,0 +1,12 @@
import React from 'react';
import styles from './Logo.css';
import {CoralLogo} from 'coral-ui';
export const Logo = () => (
<div className={styles.logo}>
<h1>
<CoralLogo stroke="#E5E5E5" />
<span>Talk</span>
</h1>
</div>
);
+7
View File
@@ -0,0 +1,7 @@
export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
@@ -1,19 +1,41 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Layout} from '../components/ui/Layout';
import {checkLogin} from '../actions/auth';
import {NotFound} from '../components/NotFound';
import {PermissionRequired} from '../components/PermissionRequired';
class LayoutContainer extends Component {
componentWillMount () {
this.props.checkLogin();
}
render () {
const {isAdmin, loggedIn} = this.props.auth;
if (!loggedIn) {
return <NotFound />;
}
if (!isAdmin && loggedIn) {
return <PermissionRequired />;
}
return <Layout {...this.props} />;
}
}
LayoutContainer.propTypes = {};
const mapStateToProps = () => ({});
const mapStateToProps = state => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = (dispatch) => ({dispatch});
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin()),
});
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(LayoutContainer);
@@ -1,4 +1,3 @@
@custom-media --big-viewport (min-width: 780px);
.listContainer {
@@ -6,8 +5,13 @@
margin: 0 auto;
}
.tabBar {
background: #9E9E9E;
}
.tab {
flex: 1;
color: white;
}
@media (--big-viewport) {
@@ -61,9 +61,9 @@ class ModerationQueue extends React.Component {
return (
<div>
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
<div className='mdl-tabs__tab-bar'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<a href='#pending' onClick={() => this.onTabClick('pending')}
className={`mdl-tabs__tab is-active ${styles.tab}`}>{lang.t('modqueue.pending')}</a>
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.pending')}</a>
<a href='#rejected' onClick={() => this.onTabClick('rejected')}
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')}</a>
<a href='#flagged' onClick={() => this.onTabClick('flagged')}
+8 -5
View File
@@ -1,12 +1,15 @@
export const base = '/api/v1';
export const getInit = (method, body) => {
const headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
});
let init = {
method,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
}),
credentials: 'same-origin'
};
const init = {method, headers};
if (method.toLowerCase() !== 'get') {
init.body = JSON.stringify(body);
}
+28
View File
@@ -0,0 +1,28 @@
import {Map} from 'immutable';
import * as actions from '../constants/auth';
const initialState = Map({
loggedIn: false,
user: null,
isAdmin: false
});
export default function auth (state = initialState, action) {
switch (action.type) {
case actions.CHECK_LOGIN_FAILURE:
return state
.set('loggedIn', false)
.set('user', null);
case actions.CHECK_LOGIN_SUCCESS:
return state
.set('loggedIn', true)
.set('isAdmin', action.isAdmin)
.set('user', action.user);
case actions.LOGOUT_SUCCESS:
return state
.set('loggedIn', false)
.set('user', null);
default :
return state;
}
}
+3 -1
View File
@@ -2,11 +2,13 @@ import {combineReducers} from 'redux';
import comments from 'reducers/comments';
import settings from 'reducers/settings';
import community from 'reducers/community';
import auth from 'reducers/auth';
// Combine all reducers into a main one
export default combineReducers({
settings,
comments,
community
community,
auth
});
@@ -34,7 +34,11 @@ export default store => next => action => {
// Get comments to fill each of the three lists on the mod queue
const fetchModerationQueueComments = store =>
Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')])
Promise.all([
fetch('/api/v1/queue/comments/pending'),
fetch('/api/v1/comments?status=rejected'),
fetch('/api/v1/comments?action=flag')
])
.then(res => Promise.all(res.map(r => r.json())))
.then(res => {
res[2] = res[2].map(comment => { comment.flagged = true; return comment; });
@@ -48,7 +52,7 @@ Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/st
const updateComment = (store, comment) => {
fetch(`/api/v1/comments/${comment.get('id')}/status`, {
method: 'POST',
method: 'PUT',
headers: jsonHeader,
body: JSON.stringify({status: comment.get('status')})
})
File diff suppressed because it is too large Load Diff
+15 -13
View File
@@ -61,8 +61,8 @@ class CommentStream extends Component {
// Set up messaging between embedded Iframe an parent component
// Using recommended Pym init code which violates .eslint standards
const pym = new Pym.Child({polling: 100});
const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl)[1];
this.props.getStream(path);
const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl);
this.props.getStream(path && path[1] || window.location);
}
render () {
@@ -84,8 +84,9 @@ class CommentStream extends Component {
const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
const {loggedIn, user} = this.props.auth;
return <div>
const {actions, users, comments} = this.props.items;
const {loggedIn, user, showSignInDialog} = this.props.auth;
return <div className={showSignInDialog ? 'expandForSignin' : ''}>
{
rootItem
? <div>
@@ -105,16 +106,16 @@ class CommentStream extends Component {
id={rootItemId}
premod={this.props.config.moderation}
reply={false}
canPost={loggedIn}
author={user}
/>
{!loggedIn && <SignInContainer />}
</div>
{
rootItem.comments && rootItem.comments.map((commentId) => {
const comment = this.props.items.comments[commentId];
const comment = comments[commentId];
return <div className="comment" key={commentId}>
<hr aria-hidden={true}/>
<AuthorName name={comment.username}/>
<AuthorName author={users[comment.author_id]}/>
<PubDate created_at={comment.created_at}/>
<Content body={comment.body}/>
<div className="commentActionsLeft">
@@ -124,7 +125,7 @@ class CommentStream extends Component {
<LikeButton
addNotification={this.props.addNotification}
id={commentId}
like={this.props.items.actions[comment.like]}
like={actions[comment.like]}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
addItem={this.props.addItem}
@@ -135,7 +136,7 @@ class CommentStream extends Component {
<FlagButton
addNotification={this.props.addNotification}
id={commentId}
flag={this.props.items.actions[comment.flag]}
flag={actions[comment.flag]}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
addItem={this.props.addItem}
@@ -151,6 +152,7 @@ class CommentStream extends Component {
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
id={rootItemId}
author={user}
parent_id={commentId}
premod={this.props.config.moderation}
showReply={comment.showReply}/>
@@ -160,13 +162,13 @@ class CommentStream extends Component {
let reply = this.props.items.comments[replyId];
return <div className="reply" key={replyId}>
<hr aria-hidden={true}/>
<AuthorName name={reply.username}/>
<AuthorName author={users[comment.author_id]}/>
<PubDate created_at={reply.created_at}/>
<Content body={reply.body}/>
<div className="replyActionsLeft">
<ReplyButton
updateItem={this.props.updateItem}
id={replyId}/>
parent_id={reply.parent_id}/>
<LikeButton
addNotification={this.props.addNotification}
id={replyId}
@@ -188,8 +190,8 @@ class CommentStream extends Component {
updateItem={this.props.updateItem}
currentUser={this.props.auth.user}/>
<PermalinkButton
comment_id={reply.comment_id}
asset_id={reply.comment_id}
comment_id={reply.parent_id}
asset_id={rootItemId}
/>
</div>
</div>;
+6 -2
View File
@@ -3,8 +3,12 @@ body {
font-family: 'Open Sans', sans-serif;
width: 100%;
font-size: 12px;
margin: 0;
min-height: 700px;
margin: 0px;
padding: 0px 0px 50px 0px;
}
.expandForSignin {
min-height: 550px;
}
button {
+23 -6
View File
@@ -3,6 +3,7 @@ import translations from './../translations';
const lang = new I18n(translations);
import * as actions from '../constants/auth';
import {base, handleResp, getInit} from '../helpers/response';
import {addItem} from './items';
// Dialog Actions
export const showSignInDialog = () => ({type: actions.SHOW_SIGNIN_DIALOG});
@@ -19,8 +20,8 @@ export const cleanState = () => ({type: actions.CLEAN_STATE});
// Sign In Actions
const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
const signInSuccess = (user) => ({type: actions.FETCH_SIGNIN_SUCCESS, user});
const signInFailure = (error) => ({type: actions.FETCH_SIGNIN_FAILURE, error});
const signInSuccess = user => ({type: actions.FETCH_SIGNIN_SUCCESS, user});
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
export const fetchSignIn = (formData) => dispatch => {
dispatch(signInRequest());
@@ -29,6 +30,7 @@ export const fetchSignIn = (formData) => dispatch => {
.then(({user}) => {
dispatch(hideSignInDialog());
dispatch(signInSuccess(user));
dispatch(addItem(user, 'users'));
})
.catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError'))));
};
@@ -54,8 +56,10 @@ export const facebookCallback = (err, data) => dispatch => {
return;
}
try {
dispatch(signInFacebookSuccess(JSON.parse(data)));
const user = JSON.parse(data);
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(addItem(user, 'users'));
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -87,9 +91,9 @@ const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUE
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
export const fetchForgotPassword = () => dispatch => {
dispatch(forgotPassowordRequest());
fetch(`${base}/user/request-password-reset`, getInit('POST'))
export const fetchForgotPassword = email => dispatch => {
dispatch(forgotPassowordRequest(email));
fetch(`${base}/user/request-password-reset`, getInit('POST', {email}))
.then(handleResp)
.then(() => dispatch(forgotPassowordSuccess()))
.catch(error => dispatch(forgotPassowordFailure(error)));
@@ -114,3 +118,16 @@ export const logout = () => dispatch => {
export const validForm = () => ({type: actions.VALID_FORM});
export const invalidForm = error => ({type: actions.INVALID_FORM, error});
// Check Login
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
const checkLoginSuccess = user => ({type: actions.CHECK_LOGIN_SUCCESS, user});
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
fetch(`${base}/auth`, getInit('GET'))
.then(handleResp)
.then(user => dispatch(checkLoginSuccess(user)))
.catch(error => dispatch(checkLoginFailure(error)));
};
+16 -12
View File
@@ -15,7 +15,7 @@ const getInit = (method, body) => {
};
const init = {method, headers};
if (method.toLowerCase() !== 'get') {
if (body) {
init.body = JSON.stringify(body);
}
@@ -23,6 +23,9 @@ const getInit = (method, body) => {
};
const responseHandler = response => {
if (response.status === 204) {
return;
}
return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
};
/**
@@ -103,8 +106,16 @@ export function getStream (assetUrl) {
/* Add items to the store */
const itemTypes = Object.keys(json);
for (let i = 0; i < itemTypes.length; i++ ) {
for (let j = 0; j < json[itemTypes[i]].length; j++ ) {
dispatch(addItem(json[itemTypes[i]][j], itemTypes[i]));
if (itemTypes[i] === 'actions') {
for (let j = 0; j < json[itemTypes[i]].length; j++ ) {
let action = json[itemTypes[i]][j];
action.id = `${action.action_type}_${action.item_id}`;
dispatch(addItem(action, 'actions'));
}
} else {
for (let j = 0; j < json[itemTypes[i]].length; j++ ) {
dispatch(addItem(json[itemTypes[i]][j], itemTypes[i]));
}
}
}
@@ -199,8 +210,6 @@ export function postItem (item, type, id) {
};
}
//http://localhost:16180/v1/action/flag/user/user_89654/on/item/87e418c5-aafb-4eb7-9ce4-78f28793782a
/*
* PostAction
* Posts an action to an item
@@ -243,14 +252,9 @@ export function postAction (item_id, action_type, user_id, item_type) {
*
*/
export function deleteAction (item_id, action_type, user_id, item_type) {
export function deleteAction (action_id) {
return () => {
const action = {
action_type,
user_id
};
return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action))
return fetch(`/api/v1/actions/${action_id}`, {method: 'DELETE'})
.then(responseHandler);
};
}
+4
View File
@@ -27,3 +27,7 @@ export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
export const INVALID_FORM = 'INVALID_FORM';
export const VALID_FORM = 'VALID_FORM';
export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
+22 -1
View File
@@ -8,6 +8,8 @@ const initialState = Map({
showSignInDialog: false,
view: 'SIGNIN',
error: '',
passwordRequestSuccess: null,
passwordRequestFailure: null,
successSignUp: false
});
@@ -22,6 +24,8 @@ export default function auth (state = initialState, action) {
showSignInDialog: false,
view: 'SIGNIN',
error: '',
passwordRequestFailure: null,
passwordRequestSuccess: null,
successSignUp: false
}));
case actions.CHANGE_VIEW :
@@ -33,6 +37,14 @@ export default function auth (state = initialState, action) {
case actions.FETCH_SIGNIN_REQUEST:
return state
.set('isLoading', true);
case actions.CHECK_LOGIN_FAILURE:
return state
.set('loggedIn', false)
.set('user', null);
case actions.CHECK_LOGIN_SUCCESS:
return state
.set('loggedIn', true)
.set('user', action.user);
case actions.FETCH_SIGNIN_SUCCESS:
return state
.set('loggedIn', true)
@@ -40,7 +52,8 @@ export default function auth (state = initialState, action) {
case actions.FETCH_SIGNIN_FAILURE:
return state
.set('isLoading', false)
.set('error', action.error);
.set('error', action.error)
.set('user', null);
case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
return state
.set('user', action.user)
@@ -70,6 +83,14 @@ export default function auth (state = initialState, action) {
case actions.VALID_FORM:
return state
.set('error', '');
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
return state
.set('passwordRequestFailure', null)
.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email');
case actions.FETCH_FORGOT_PASSWORD_FAILURE:
return state
.set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
.set('passwordRequestSuccess', null);
default :
return state;
}
@@ -1,9 +1,9 @@
import React from 'react';
const packagename = 'coral-plugin-author-name';
const AuthorName = ({name}) =>
const AuthorName = ({author}) =>
<div className={`${packagename}-text`}>
{name}
{author && author.displayName}
</div>;
export default AuthorName;
+6 -5
View File
@@ -12,7 +12,8 @@ class CommentBox extends Component {
id: PropTypes.string,
comments: PropTypes.array,
reply: PropTypes.bool,
canPost: PropTypes.bool
canPost: PropTypes.bool,
currentUser: PropTypes.object
}
state = {
@@ -21,11 +22,11 @@ class CommentBox extends Component {
}
postComment = () => {
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod} = this.props;
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod, author} = this.props;
let comment = {
body: this.state.body,
asset_id: id,
username: this.state.username
author_id: author.id
};
let related;
let parent_type;
@@ -52,7 +53,7 @@ class CommentBox extends Component {
}
render () {
const {styles, reply, canPost} = this.props;
const {styles, reply, author} = this.props;
// How to handle language in plugins? Should we have a dependency on our central translation file?
return <div>
<div
@@ -73,7 +74,7 @@ class CommentBox extends Component {
rows={3}/>
</div>
<div className={`${name}-button-container`}>
{ canPost && (
{ author && (
<button
className={`${name}-button`}
style={styles && styles.button}
+10 -6
View File
@@ -4,18 +4,22 @@ import translations from './translations.json';
const name = 'coral-plugin-flags';
const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification}) => {
const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification, currentUser}) => {
const flagged = flag && flag.current_user;
const onFlagClick = () => {
if (!currentUser) {
return;
}
if (!flagged) {
postAction(id, 'flag', '123', 'comments')
postAction(id, 'flag', currentUser.id, 'comments')
.then((action) => {
addItem({...action, current_user:true}, 'actions');
updateItem(action.item_id, action.action_type, action.id, 'comments');
let id = `${action.action_type}_${action.item_id}`;
addItem({id, current_user: action, count: flag ? flag.count + 1 : 1}, 'actions');
updateItem(action.item_id, action.action_type, id, 'comments');
});
addNotification('success', lang.t('flag-notif'));
} else {
deleteAction(id, 'flag', '123', 'comments')
deleteAction(flagged.id)
.then(() => {
updateItem(id, 'flag', '', 'comments');
});
@@ -31,7 +35,7 @@ const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, ad
: <span className={`${name}-button-text`}>{lang.t('flag')}</span>
}
<i className={`${name}-icon material-icons ${flagged && 'flaggedIcon'}`}
style={flagged && styles.flaggedIcon}
style={flagged ? styles.flaggedIcon : {}}
aria-hidden={true}>flag</i>
</button>
</div>;
+9 -5
View File
@@ -4,17 +4,21 @@ import translations from './translations.json';
const name = 'coral-plugin-flags';
const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem}) => {
const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem, currentUser}) => {
const liked = like && like.current_user;
const onLikeClick = () => {
if (!currentUser) {
return;
}
if (!liked) {
postAction(id, 'like', '123', 'comments')
postAction(id, 'like', currentUser.id, 'comments')
.then((action) => {
addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions');
updateItem(action.item_id, action.action_type, action.id, 'comments');
let id = `${action.action_type}_${action.item_id}`;
addItem({id, current_user: action, count: like ? like.count + 1 : 1}, 'actions');
updateItem(action.item_id, action.action_type, id, 'comments');
});
} else {
deleteAction(id, 'like', '123', 'comments')
deleteAction(liked.id)
.then(() => {
updateItem(like.id, 'count', like.count - 1, 'actions');
updateItem(like.id, 'current_user', false, 'actions');
@@ -5,25 +5,56 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const ForgotContent = ({changeView, ...props}) => (
<div>
<div className={styles.header}>
<h1>{lang.t('signIn.recoverPassword')}</h1>
</div>
<form onSubmit={(e) => {e.preventDefault(); props.fetchForgotPassword();}}>
<div className={styles.formField}>
<label htmlFor="email">{lang.t('signIn.email')}</label>
<input type="text" id="email" />
class ForgotContent extends React.Component {
constructor (props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit (e) {
e.preventDefault();
this.props.fetchForgotPassword(this.emailInput.value);
}
render () {
const {changeView, auth} = this.props;
const {passwordRequestSuccess, passwordRequestFailure} = auth;
return (
<div>
<div className={styles.header}>
<h1>{lang.t('signIn.recoverPassword')}</h1>
</div>
<form onSubmit={this.handleSubmit}>
<div className={styles.formField}>
<label htmlFor="email">{lang.t('signIn.email')}</label>
<input
ref={input => this.emailInput = input}
type="text"
id="email"
name="email" />
</div>
<Button type="submit" cStyle="black" className={styles.signInButton}>
{lang.t('signIn.recoverPassword')}
</Button>
{
passwordRequestSuccess
? <p className={styles.passwordRequestSuccess}>{passwordRequestSuccess}</p>
: null
}
{
passwordRequestFailure
? <p className={styles.attention}>{passwordRequestFailure}</p>
: null
}
</form>
<div className={styles.footer}>
<span>{lang.t('signIn.needAnAccount')} <a onClick={() => changeView('SIGNUP')}>{lang.t('signIn.register')}</a></span>
<span>{lang.t('signIn.alreadyHaveAnAccount')} <a onClick={() => changeView('SIGNIN')}>{lang.t('signIn.signIn')}</a></span>
</div>
</div>
<Button type="submit" cStyle="black" className={styles.signInButton}>
{lang.t('signIn.recoverPassword')}
</Button>
</form>
<div className={styles.footer}>
<span>{lang.t('signIn.needAnAccount')} <a onClick={() => changeView('SIGNUP')}>{lang.t('signIn.register')}</a></span>
<span>{lang.t('signIn.alreadyHaveAnAccount')} <a onClick={() => changeView('SIGNIN')}>{lang.t('signIn.signIn')}</a></span>
</div>
</div>
);
);
}
}
export default ForgotContent;
+13 -2
View File
@@ -106,7 +106,7 @@ input.error{
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
margin: 0px;
}
.attention {
@@ -128,4 +128,15 @@ input.error{
.action {
margin-top: 15px;
}
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral
}
@@ -18,7 +18,8 @@ import {
fetchForgotPassword,
facebookCallback,
invalidForm,
validForm
validForm,
checkLogin
} from '../../coral-framework/actions/auth';
class SignInContainer extends Component {
@@ -43,6 +44,10 @@ class SignInContainer extends Component {
this.addError = this.addError.bind(this);
}
componentWillMount () {
this.props.checkLogin();
}
componentDidMount() {
window.authCallback = this.props.facebookCallback;
const {formData} = this.state;
@@ -147,6 +152,7 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin()),
facebookCallback: (err, data) => dispatch(facebookCallback(err, data)),
fetchSignUp: formData => dispatch(fetchSignUp(formData)),
fetchSignIn: formData => dispatch(fetchSignIn(formData)),
+42
View File
@@ -0,0 +1,42 @@
import React, {PropTypes} from 'react';
const CoralLogo = ({height = '30px', width = '30px', stroke = '#FFFFFF'}) => (
<svg width={width} height={height} viewBox='0 0 381 391' version='1.1 xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink'>
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
<g id='Wordmark-Round' transform='translate(-1833.000000, -411.000000)' stroke={stroke} strokeWidth='22' strokeLinecap='round' strokeLinejoin='round'>
<g id='coralProjectLogo-2-Copy-2' transform='translate(1842.000000, 421.000000)'>
<g id='Layer_2' transform='translate(2.268750, 1.133903)'>
<rect id='Rectangle-1' fill='#F47E6B' x='0' y='0' width='358.4625' height='368.518519' rx='40'>
</rect>
<path d='M0.226875,105.679772 C13.7259375,122.688319 41.29125,131.986325 56.71875,116.792023 C67.0415625,106.586895 62.5040625,93.0934473 76.910625,63.3851852 C83.94375,48.7578348 90.523125,38.3259259 101.980313,37.5321937 C115.139063,36.6250712 131.814375,50.3452991 134.31,69.3948718 C137.826563,95.1344729 115.479375,105.339601 121.15125,123.822222 C127.390313,144.119088 157.110938,140.377208 166.52625,165.096296 C170.042813,174.394302 171.630938,190.382336 163.463438,198.319658 C149.170313,212.266667 120.584063,186.073504 80.7675,198.319658 C73.280625,200.587464 56.3784375,205.803419 51.500625,219.523647 C46.73625,233.130484 55.584375,251.046154 67.60875,260.797721 C93.245625,281.661538 119.79,254.674644 159.379688,271.909972 C181.840313,281.661538 203.053125,303.319088 208.725,330.305983 C211.674375,344.593162 209.6325,356.952707 207.704063,364.549858' id='Shape'>
</path>
</g>
<g id='Layer_3' transform='translate(43.106250, 289.145299)'>
<path d='M90.4096875,72.5698006 C78.8390625,41.3874644 64.9996875,31.4091168 54.1096875,28.234188 C41.8584375,24.7190883 30.7415625,29.0279202 17.8096875,21.2039886 C8.394375,15.4210826 3.403125,6.57663818 0.680625,0'id='Shape'></path>
</g><g id='Layer_4' transform='translate(220.068750, 209.772080)'>
<path d='M81.7884375,152.963533 C74.9821875,122.007977 61.8234375,104.77265 50.593125,94.5675214 C31.8759375,77.6723647 14.746875,77.1054131 5.218125,57.2621083 C4.4240625,55.5612536 -5.7853125,33.4501425 5.218125,17.008547 C14.52,3.06153846 33.350625,1.47407407 41.518125,0.907122507 C64.3190625,-0.907122507 73.280625,9.52478632 100.959375,13.039886 C117.180938,15.0809117 130.793438,13.4934473 139.30125,12.0193732'id='Shape'></path>
</g>
<g id='Layer_5' transform='translate(285.862500, 289.145299)'>
<path d='M74.415,2.04102564 C66.3609375,0.793732194 51.046875,-0.566951567 33.12375,5.1025641 C17.5828125,9.97834758 6.80625,18.0290598 0.9075,23.2450142'id='Shape'></path>
</g>
<g id='Layer_6' transform='translate(174.693750, 1.133903)'>
<path d='M184.109063,151.035897 C170.950313,174.507692 158.699063,180.290598 149.850938,181.311111 C133.85625,183.011966 129.091875,168.724786 104.475938,163.168661 C79.2928125,157.499145 72.2596875,169.745299 52.0678125,163.168661 C30.0609375,155.911681 18.7171875,134.821083 12.705,123.822222 C-1.588125,97.4022792 -0.3403125,71.6626781 0.5671875,51.2524217 C1.588125,29.4814815 6.125625,12.0193732 9.6421875,0.907122507'id='Shape'></path>
<path d='M183.541875,69.3948718 C170.496563,56.6951567 157.564688,45.8096866 149.28375,39.0062678 C143.385,34.1304843 134.990625,33.2233618 128.297813,36.8518519 C128.070938,36.9652422 127.844063,37.0786325 127.730625,37.1920228 C122.739375,40.1401709 119.449688,45.3561254 118.8825,51.1390313 C118.201875,59.0763533 117.0675,71.4358974 114.912188,82.8883191 C114.118125,87.0837607 107.085,103.865527 89.7290625,110.668946 C77.2509375,115.544729 62.0503125,110.668946 53.4290625,101.597721 C40.38375,87.7641026 44.8078125,66.9002849 47.416875,55.2210826 C53.5425,26.760114 73.3940625,9.07122507 82.6959375,1.81424501'id='Shape'></path>
</g>
<g id='Layer_7' transform='translate(3.403125, 179.156695)'>
<path d='M0.1134375,0.226780627 C2.949375,6.34985755 8.394375,16.1014245 18.2634375,25.3994302 C27.67875,34.2438746 37.3209375,39.0062678 43.4465625,41.5008547'id='Shape'></path>
</g>
</g>
</g>
</g>
</svg>
);
CoralLogo.propTypes = {
height: PropTypes.string,
width: PropTypes.string,
stroke: PropTypes.string
};
export default CoralLogo;
+9
View File
@@ -0,0 +1,9 @@
.type--approve {
background: #00796b;
color: rgba(255, 255, 255, 0.901961);
}
.type--reject {
background: #d32f2f ;
color: rgba(255, 255, 255, 0.901961);
}
+11
View File
@@ -0,0 +1,11 @@
import React from 'react';
import styles from './FabButton.css';
import {FABButton, Icon} from 'react-mdl';
const FabButton = ({cStyle = 'local', icon, className, ...props}) => (
<FABButton className={`${styles[`type--${cStyle}`]} ${className ? className : ''}`} {...props}>
<Icon name={icon} />
</FABButton>
);
export default FabButton;
+2
View File
@@ -1 +1,3 @@
export {default as Dialog} from './components/Dialog';
export {default as CoralLogo} from './components/CoralLogo';
export {default as FabButton} from './components/FabButton';