Merge branch 'master' into bulk-approve-reject-actions

This commit is contained in:
Facu
2017-10-19 14:12:08 -03:00
committed by GitHub
44 changed files with 1973 additions and 1165 deletions
@@ -4,6 +4,7 @@ import Layout from 'coral-admin/src/components/ui/Layout';
import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
import Recaptcha from 'react-recaptcha';
import cn from 'classnames';
class AdminLogin extends React.Component {
@@ -34,19 +35,22 @@ class AdminLogin extends React.Component {
render () {
const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props;
const signInForm = (
<form onSubmit={this.handleSignIn}>
<form className="talk-admin-login-sign-in" onSubmit={this.handleSignIn}>
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
id="email"
label='Email Address'
value={this.state.email}
onChange={(e) => this.setState({email: e.target.value})} />
<TextField
id="password"
label='Password'
value={this.state.password}
onChange={(e) => this.setState({password: e.target.value})}
type='password' />
<div style={{height: 10}}></div>
<Button
className="talk-admin-login-sign-in-button"
type='submit'
cStyle='black'
full
@@ -90,7 +94,7 @@ class AdminLogin extends React.Component {
);
return (
<Layout fixedDrawer restricted={true}>
<div className={styles.loginLayout}>
<div className={cn(styles.loginLayout, 'talk-admin-login')}>
<h1 className={styles.loginHeader}>Team sign in</h1>
<p className={styles.loginCTA}>Sign in to interact with your community.</p>
{ this.state.requestPassword ? requestPasswordForm : signInForm }
+18 -2
View File
@@ -2,17 +2,25 @@ import React from 'react';
import TagsInput from 'react-tagsinput';
import styles from './TagsInput.css';
import AutosizeInput from 'react-input-autosize';
import PropTypes from 'prop-types';
import cn from 'classnames';
const autosizingRenderInput = ({onChange, value, addTag: _, ...other}) =>
<AutosizeInput type='text' onChange={onChange} value={value} {...other} />;
export default (props) => {
autosizingRenderInput.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
addTag: PropTypes.func,
};
const TagsInputComponent = ({className = '', ...props}) => {
return (
<TagsInput
addOnBlur={true}
addOnPaste={true}
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
className={styles.root}
className={cn(styles.root, 'tags-input', className)}
focusedClassName={styles.rootFocus}
renderInput={autosizingRenderInput}
{...props}
@@ -29,3 +37,11 @@ export default (props) => {
/>
);
};
TagsInputComponent.propTypes = {
className: PropTypes.string,
inputProps: PropTypes.object,
tagProps: PropTypes.object,
};
export default TagsInputComponent;
@@ -1,13 +1,14 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button} from 'coral-ui';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
const AddOrganizationName = (props) => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
<div className={styles.step}>
<div className={cn(styles.step, 'talk-install-step-2')}>
<p>{t('install.add_organization.description')}</p>
<div className={styles.form}>
<form onSubmit={handleSettingsSubmit}>
@@ -19,11 +20,17 @@ const AddOrganizationName = (props) => {
onChange={handleSettingsChange}
showErrors={install.showErrors}
errorMsg={install.errors.organizationName} />
<Button type="submit" cStyle='black' full>{t('install.add_organization.save')}</Button>
<Button className="talk-install-step-2-save-button" type="submit" cStyle='black' full>{t('install.add_organization.save')}</Button>
</form>
</div>
</div>
);
};
AddOrganizationName.propTypes = {
handleSettingsChange: PropTypes.func,
handleSettingsSubmit: PropTypes.func,
install: PropTypes.object,
};
export default AddOrganizationName;
@@ -1,13 +1,14 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
const InitialStep = (props) => {
const {handleUserChange, handleUserSubmit, install} = props;
return (
<div className={styles.step}>
<div className={cn(styles.step, 'talk-install-step-3')}>
<div className={styles.form}>
<form onSubmit={handleUserSubmit}>
<TextField
@@ -53,7 +54,7 @@ const InitialStep = (props) => {
{
!props.install.isLoading ?
<Button cStyle='black' type="submit" full>{t('install.create.save')}</Button>
<Button className="talk-install-step-3-save-button" cStyle='black' type="submit" full>{t('install.create.save')}</Button>
:
<Spinner />
}
@@ -64,4 +65,10 @@ const InitialStep = (props) => {
);
};
InitialStep.propTypes = {
handleUserChange: PropTypes.func,
handleUserSubmit: PropTypes.func,
install: PropTypes.object,
};
export default InitialStep;
@@ -2,12 +2,13 @@ import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
import {Link} from 'react-router';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
const InitialStep = () => {
return (
<div className={`${styles.step} ${styles.finalStep}`}>
<div className={cn(styles.step, styles.finalStep, 'talk-install-step-5')}>
<p>{t('install.final.description')}</p>
<Button raised><Link to='/admin'>{t('install.final.launch')}</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">{t('install.final.close')}</a></Button>
@@ -1,17 +1,22 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
const InitialStep = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
<div className={cn(styles.step, 'talk-install-step-1')}>
<p>{t('install.initial.description')}</p>
<Button cStyle='green' onClick={nextStep} raised>{t('install.initial.submit')}</Button>
<Button className={'talk-install-get-started-button'} cStyle='green' onClick={nextStep} raised>{t('install.initial.submit')}</Button>
</div>
);
};
InitialStep.propTypes = {
nextStep: PropTypes.func,
};
export default InitialStep;
@@ -2,26 +2,34 @@ import React from 'react';
import styles from './style.css';
import {Button, Card} from 'coral-ui';
import TagsInput from 'coral-admin/src/components/TagsInput';
import PropTypes from 'prop-types';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
const PermittedDomainsStep = (props) => {
const {finishInstall, install, handleDomainsChange} = props;
const domains = install.data.settings.domains.whitelist;
return (
<div className={styles.step}>
<div className={cn(styles.step, 'talk-install-step-4')}>
<h3>{t('install.permitted_domains.title')}</h3>
<Card className={styles.card}>
<p>{t('install.permitted_domains.description')}</p>
<TagsInput
className="talk-install-step-4-permited-domains-input"
value={domains}
inputProps={{placeholder: 'URL'}}
onChange={(tags) => handleDomainsChange(tags)}
/>
</Card>
<Button cStyle='green' onClick={finishInstall} raised>{t('install.permitted_domains.submit')}</Button>
<Button className="talk-install-step-4-save-button" cStyle='green' onClick={finishInstall} raised>{t('install.permitted_domains.submit')}</Button>
</div>
);
};
PermittedDomainsStep.propTypes = {
finishInstall: PropTypes.func,
handleDomainsChange: PropTypes.func,
install: PropTypes.object,
};
export default PermittedDomainsStep;
@@ -1,9 +1,9 @@
import React from 'react';
import {Button} from 'coral-ui';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
export default ({status, onClick}) => (
const CloseCommentsInfo = ({status, onClick}) => (
status === 'open' ? (
<div className="close-comments-intro-wrapper">
<p>
@@ -20,3 +20,10 @@ export default ({status, onClick}) => (
</div>
)
);
CloseCommentsInfo.propTypes = {
status: PropTypes.string,
onClick: PropTypes.func,
};
export default CloseCommentsInfo;
@@ -1,12 +1,10 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import PropTypes from 'prop-types';
import {updateOpenStatus, updateConfiguration} from 'coral-embed-stream/src/actions/asset';
import CloseCommentsInfo from '../components/CloseCommentsInfo';
import ConfigureCommentStream from '../components/ConfigureCommentStream';
import t, {timeago} from 'coral-framework/services/i18n';
class ConfigureStreamContainer extends Component {
@@ -134,6 +132,14 @@ const mapDispatchToProps = (dispatch) => ({
updateConfiguration: (newConfig) => dispatch(updateConfiguration(newConfig)),
});
ConfigureStreamContainer.propTypes = {
updateStatus: PropTypes.func,
closedTimeout: PropTypes.string,
created_at: PropTypes.string,
updateConfiguration: PropTypes.func,
asset: PropTypes.object,
};
export default compose(
connect(mapStateToProps, mapDispatchToProps)
)(ConfigureStreamContainer);
@@ -1,10 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
import {TransitionGroup} from 'react-transition-group';
import {forEachError} from 'coral-framework/utils';
import Comment from '../containers/Comment';
import NoComments from './NoComments';
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
@@ -144,53 +145,82 @@ class AllCommentsPane extends React.Component {
} = this.props;
const {loadingState} = this.state;
const view = this.getVisibleComments();
const visibleComments = this.getVisibleComments();
return (
<div className="talk-stream-comments-container">
<NewCount
count={comments.nodes.length - view.length}
loadMore={this.viewNewComments}
/>
<TransitionGroup component='div' className="embed__stream">
{view.map((comment) => {
return (
<Comment
commentClassNames={commentClassNames}
data={data}
root={root}
disableReply={disableReply}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
depth={0}
postComment={postComment}
asset={asset}
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
loadMore={loadNewReplies}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
comment={comment}
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
editComment={editComment}
emit={emit}
/>
);
})}
</TransitionGroup>
<LoadMore
topLevel={true}
moreComments={asset.comments.hasNextPage}
loadMore={this.loadMore}
loadingState={loadingState}
/>
<div>
{visibleComments.length ? (
<div className="talk-stream-comments-container">
<NewCount
count={comments.nodes.length - visibleComments.length}
loadMore={this.viewNewComments}
/>
<TransitionGroup component='div' className="embed__stream">
{visibleComments.map((comment) => {
return (
<Comment
commentClassNames={commentClassNames}
data={data}
root={root}
disableReply={disableReply}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
depth={0}
postComment={postComment}
asset={asset}
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
loadMore={loadNewReplies}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
comment={comment}
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
editComment={editComment}
emit={emit}
/>
);
})}
</TransitionGroup>
<LoadMore
topLevel={true}
moreComments={asset.comments.hasNextPage}
loadMore={this.loadMore}
loadingState={loadingState}
/>
</div>
) : (
<NoComments assetClosed={asset.isClosed} />
)}
</div>
);
}
}
AllCommentsPane.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
comments: PropTypes.object,
commentClassNames: PropTypes.array,
setActiveReplyBox: PropTypes.func,
activeReplyBox: PropTypes.string,
notify: PropTypes.func,
disableReply: PropTypes.bool,
postComment: PropTypes.func,
asset: PropTypes.object,
currentUser: PropTypes.object,
postFlag: PropTypes.func,
postDontAgree: PropTypes.func,
loadNewReplies: PropTypes.func,
deleteAction: PropTypes.func,
showSignInDialog: PropTypes.func,
charCountEnable: PropTypes.bool,
maxCharCount: PropTypes.number,
editComment: PropTypes.func,
emit: PropTypes.func,
};
export default AllCommentsPane;
@@ -519,6 +519,7 @@ export default class Comment extends React.Component {
: <div>
<Slot
fill="commentContent"
className='talk-stream-comment-content'
defaultComponent={CommentContent}
{...slotProps}
queryData={queryData}
@@ -0,0 +1,6 @@
.commentTombstone {
background-color: #F0F0F0;
text-align: center;
padding: 1em;
color: #3E4F71;
}
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import styles from './CommentTombstone.css';
// Render in place of a Comment when the author of the comment is <action>
class CommentTombstone extends React.Component {
@@ -19,14 +20,9 @@ class CommentTombstone extends React.Component {
render() {
return (
<div>
<div className="talk-comment-tombstone">
<hr aria-hidden={true} />
<p style={{
backgroundColor: '#F0F0F0',
textAlign: 'center',
padding: '1em',
color: '#3E4F71',
}}>
<p className={styles.commentTombstone}>
{this.getCopy()}
</p>
</div>
@@ -34,4 +30,8 @@ class CommentTombstone extends React.Component {
}
}
CommentTombstone.propTypes = {
action: PropTypes.string,
};
export default CommentTombstone;
@@ -75,13 +75,13 @@ export default class Embed extends React.Component {
activeTab={activeTab}
id='talk-embed-stream-tab-content'
>
<TabPane tabId={'stream'}>
<TabPane tabId={'stream'} className={'talk-embed-stream-comments-tab-pane'}>
<Stream data={data} root={root} />
</TabPane>
<TabPane tabId={'profile'}>
<TabPane tabId={'profile'} className={'talk-embed-stream-profile-tab-pane'}>
<ProfileContainer />
</TabPane>
<TabPane tabId={'config'}>
<TabPane tabId={'config'} className={'talk-embed-stream-configuration-tab-pane'}>
<ConfigureStreamContainer />
</TabPane>
</TabContent>
@@ -0,0 +1,6 @@
.message {
padding: 20px 10px;
background-color: #f7f7f7;
margin: 20px 0;
text-align: center;
}
@@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
import styles from './NoComments.css';
const NoComments = ({assetClosed}) => (
<div className="talk-stream-no-comments">
{assetClosed ? (
<div className={cn(styles.message, 'talk-stream-no-comments-closed-message')}>
{t('stream.no_comments_and_closed')}
</div>
) : (
<div className={cn(styles.message, 'talk-stream-no-comments-message')}>
{t('stream.no_comments')}
</div>
)}
</div>
);
NoComments.propTypes = {
assetClosed: PropTypes.bool,
};
export default NoComments;
@@ -303,11 +303,32 @@ class Stream extends React.Component {
}
Stream.propTypes = {
activeStreamTab: PropTypes.string,
data: PropTypes.object,
root: PropTypes.object,
activeReplyBox: PropTypes.string,
setActiveReplyBox: PropTypes.func,
commentClassNames: PropTypes.array,
setActiveStreamTab: PropTypes.func,
loadMoreComments: PropTypes.func,
postFlag: PropTypes.func,
postDontAgree: PropTypes.func,
deleteAction: PropTypes.func,
showSignInDialog: PropTypes.func,
loadNewReplies: PropTypes.func,
auth: PropTypes.object,
emit: PropTypes.func,
sortOrder: PropTypes.string,
sortBy: PropTypes.string,
loading: PropTypes.bool,
editName: PropTypes.func,
appendItemArray: PropTypes.func,
updateItem: PropTypes.func,
viewAllComments: PropTypes.func,
notify: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired,
// edit a comment, passed (id, asset_id, { body })
editComment: PropTypes.func
editComment: PropTypes.func,
userIsDegraged: PropTypes.bool,
};
export default Stream;
@@ -6,9 +6,9 @@ import styles from './StreamTabPanel.css';
class StreamTabPanel extends React.Component {
render() {
const {activeTab, setActiveTab, tabs, tabPanes, sub, loading} = this.props;
const {activeTab, setActiveTab, tabs, tabPanes, sub, loading, ...rest} = this.props;
return (
<div>
<div {...rest}>
<TabBar activeTab={activeTab} onTabClick={setActiveTab} sub={sub}>
{tabs}
</TabBar>
@@ -26,6 +26,7 @@ class StreamTabPanel extends React.Component {
StreamTabPanel.propTypes = {
activeTab: PropTypes.string.isRequired,
setActiveTab: PropTypes.func.isRequired,
loading: PropTypes.bool,
tabs: PropTypes.oneOfType([
PropTypes.element,
PropTypes.arrayOf(PropTypes.element)
@@ -17,6 +17,7 @@ function getFragmentId(assetId) {
* AutomaticAssetClosure updates the graphql state of the provide asset
* to `isClosed=true` when passed `closedAt`.
*/
class AutomaticAssetClosure extends React.Component {
static contextTypes = {
client: PropTypes.object.isRequired,
@@ -1,10 +1,11 @@
import React from 'react';
import styles from './NotLoggedIn.css';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
export default ({showSignInDialog}) => (
<div className={styles.message}>
<div className={cn(styles.message, 'talk-embed-stream-not-logged-in')}>
<div>
<a onClick={showSignInDialog}>{t('settings.sign_in')}</a> {t('settings.to_access')}
</div>
@@ -4,7 +4,7 @@ import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {withQuery} from 'coral-framework/hocs';
import Slot from 'coral-framework/components/Slot';
import cn from 'classnames';
import {link} from 'coral-framework/services/pym';
import NotLoggedIn from '../components/NotLoggedIn';
import {Spinner} from 'coral-ui';
@@ -71,21 +71,23 @@ class ProfileContainer extends Component {
const emailAddress = localProfile && localProfile.id;
return (
<div className='talk-embed-stream-profile-container'>
<div className="talk-my-profile talk-embed-stream-profile-container">
<h2>{me.username}</h2>
{emailAddress ? <p>{emailAddress}</p> : null}
<Slot
fill="profileSections"
data={data}
queryData={{root}}
/>
<hr />
<h3>{t('framework.my_comments')}</h3>
{me.comments.nodes.length
? <CommentHistory data={data} root={root} comments={me.comments} link={link} loadMore={this.loadMore}/>
: <p>{t('user_no_comment')}</p>}
<div className={cn('talk-my-profile-comment-history', {
'talk-my-profile-comment-history-no-comments': !me.comments.nodes.length
})}>
{me.comments.nodes.length
? <CommentHistory data={data} root={root} comments={me.comments} link={link} loadMore={this.loadMore}/>
: <p className='talk-my-profile-comment-history-no-comments-cta'>{t('user_no_comment')}</p>}
</div>
</div>
);
}
+7 -3
View File
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import Comment from './Comment';
import styles from './CommentHistory.css';
import LoadMore from './LoadMore';
import {forEachError} from 'plugin-api/beta/client/utils';
@@ -25,7 +24,7 @@ class CommentHistory extends React.Component {
render() {
const {link, comments, data, root} = this.props;
return (
<div className={`${styles.header} commentHistory`}>
<div>
<div className="commentHistory__list">
{comments.nodes.map((comment, i) => {
return <Comment
@@ -49,7 +48,12 @@ class CommentHistory extends React.Component {
}
CommentHistory.propTypes = {
comments: PropTypes.object.isRequired
comments: PropTypes.object.isRequired,
loadMore: PropTypes.func,
notify: PropTypes.func,
link: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object
};
export default CommentHistory;