Merge branch 'master' into i18n-refactor

Conflicts:
	client/coral-sign-in/components/FakeComment.js
	client/coral-sign-in/components/UserBox.js
This commit is contained in:
Chi Vinh Le
2017-05-29 20:23:27 +07:00
174 changed files with 14458 additions and 2048 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ import {
UPDATE_ASSETS
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
/**
* Action disptacher related to assets
+26 -6
View File
@@ -1,6 +1,7 @@
import bowser from 'bowser';
import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/request';
import * as Storage from 'coral-framework/helpers/storage';
import coralApi from 'coral-framework/helpers/response';
import {handleAuthToken} from 'coral-framework/actions/auth';
//==============================================================================
@@ -9,16 +10,31 @@ import {handleAuthToken} from 'coral-framework/actions/auth';
export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => {
dispatch({type: actions.LOGIN_REQUEST});
const params = {method: 'POST', body: {email, password}};
const params = {
method: 'POST',
body: {
email,
password
}
};
if (recaptchaResponse) {
params.headers = {'X-Recaptcha-Response': recaptchaResponse};
params.headers = {
'X-Recaptcha-Response': recaptchaResponse
};
}
return coralApi('/auth/local', params)
.then(({user, token}) => {
if (!user) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
dispatch(handleAuthToken(token));
dispatch(checkLoginSuccess(user));
})
@@ -52,7 +68,9 @@ const forgotPassowordFailure = () => ({
export const requestPasswordReset = (email) => (dispatch) => {
dispatch(forgotPassowordRequest(email));
return coralApi('/account/password/reset', {method: 'POST', body: {email}})
const redirectUri = location.href;
return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch((error) => dispatch(forgotPassowordFailure(error)));
};
@@ -81,7 +99,9 @@ export const checkLogin = () => (dispatch) => {
return coralApi('/auth')
.then(({user}) => {
if (!user) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
+1 -1
View File
@@ -14,7 +14,7 @@ import {
HIDE_SUSPENDUSER_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
export const fetchAccounts = (query = {}) => (dispatch) => {
+1 -1
View File
@@ -1,4 +1,4 @@
import coralApi from 'coral-framework/helpers/response';
import coralApi from 'coral-framework/helpers/request';
import * as actions from '../constants/install';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
+1 -1
View File
@@ -1,4 +1,4 @@
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED';
+1 -1
View File
@@ -1,4 +1,4 @@
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
import * as userTypes from '../constants/users';
/**
@@ -73,6 +73,8 @@
font-weight: 500;
background-color: transparent;
transition: background-color 200ms;
font-weight: 100;
letter-spacing: .8;
&:hover {
background-color: #232323;
@@ -80,6 +82,7 @@
&.active {
background-color: #232323;
font-weight: 400;
}
}
@@ -3,12 +3,13 @@
background: #989797;
margin: 2px;
vertical-align: middle;
padding: 4px 7px;
padding: 1px 7px;
border-radius: 2px;
margin-left: 10px;
margin-left: 2px;
line-height: 20px;
box-sizing: border-box;
height: 27px;
height: 21px;
right: 0;
margin-top: -2px;
font-size: 12px;
}
@@ -11,7 +11,7 @@ const ModerationHeader = (props) => (
props.asset ?
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
<Link className="mdl-tabs__tab" to="/admin/moderate">{t('modqueue.all_streams')}</Link>
<a className="mdl-tabs__tab" href={props.asset.url}>
<a className="mdl-tabs__tab" href={props.asset.url} target="_blank">
<span>{props.asset.title}</span>
<Icon className={styles.settingsButton} name="open_in_new"/>
</a>
@@ -18,18 +18,21 @@
.tab {
flex: 1;
color: white;
color: #EEEEEE;
text-transform: capitalize;
font-weight: 500;
font-weight: 100;
font-size: 15px;
letter-spacing: 1px;
transition: border-bottom 200ms;
padding: 0px 5px;
margin-right: 30px;
}
.active {
color: white;
box-sizing: border-box;
border-bottom: solid 5px #F36451;
border-bottom: solid 4px #F36451;
font-weight: 400;
}
.active > span {
@@ -97,7 +100,7 @@ span {
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-weight: 400;
font-size: 15px;
letter-spacing: 1px;
transition: opacity 200ms;
@@ -371,6 +374,7 @@ span {
padding: 10px 15px;
box-sizing: border-box;
border-radius: 2px;
bor
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
> div {
@@ -388,6 +392,7 @@ span {
font-size: 13px;
letter-spacing: 0.7px;
font-weight: 400;
border-bottom: 0px;
}
label {
@@ -421,7 +426,8 @@ span {
.tabIcon {
position: relative;
top: 7px;
top: 2px;
font-size: 16px;
}
.username {
+3
View File
@@ -8,6 +8,9 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.js';
import {loadPluginsTranslations} from 'coral-framework/helpers/plugins';
loadPluginsTranslations();
render(
<ApolloProvider client={client} store={store}>
+3 -3
View File
@@ -2,12 +2,12 @@ import React from 'react';
import {Router, Route, browserHistory} from 'react-router';
import Embed from './containers/Embed';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import {LoginContainer} from 'coral-sign-in/containers/LoginContainer';
const routes = (
<div>
<Route exact path="/embed/stream/login" component={SignInContainer}/>
<Route exact path="*" component={Embed}/>
<Route exact path="/embed/stream/login" component={LoginContainer}/>
<Route path="*" component={Embed}/>
</div>
);
@@ -1,72 +1,67 @@
import React from 'react';
import t from 'coral-framework/services/i18n';
import Stream from '../containers/Stream';
import Slot from 'coral-framework/components/Slot';
import {can} from 'coral-framework/services/perms';
import t from 'coral-framework/services/i18n';
import {TabBar, Tab, TabContent, Button} from 'coral-ui';
import Stream from '../containers/Stream';
import Count from 'coral-plugin-comment-count/CommentCount';
import UserBox from 'coral-sign-in/components/UserBox';
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
import ConfigureStreamContainer
from 'coral-configure/containers/ConfigureStreamContainer';
export default class Embed extends React.Component {
changeTab = (tab) => {
switch(tab) {
switch (tab) {
case 0:
this.props.setActiveTab('stream');
break;
case 1:
this.props.setActiveTab('profile');
// TODO: move data fetching to profile container.
// TODO: move data fetching to profile container.
this.props.data.refetch();
break;
case 2:
this.props.setActiveTab('config');
// TODO: move data fetching to config container.
// TODO: move data fetching to config container.
this.props.data.refetch();
break;
}
}
};
handleShowProfile = () => this.props.setActiveTab('profile');
render () {
const {activeTab, logout, viewAllComments, commentId} = this.props;
render() {
const {activeTab, viewAllComments, commentId} = this.props;
const {asset: {totalCommentCount}} = this.props.root;
const {loggedIn, user} = this.props.auth;
const userBox = <UserBox user={user} onLogout={logout} onShowProfile={this.handleShowProfile}/>;
const {user} = this.props.auth;
return (
<div>
<div className="commentStream">
<TabBar onChange={this.changeTab} activeTab={activeTab}>
<Tab><Count count={totalCommentCount}/></Tab>
<Tab><Count count={totalCommentCount} /></Tab>
<Tab>{t('framework.my_profile')}</Tab>
<Tab restricted={!can(user, 'UPDATE_CONFIG')}>{t('framework.configure_stream')}</Tab>
</TabBar>
{
commentId &&
<Button
cStyle='darkGrey'
style={{float: 'right'}}
onClick={viewAllComments}
>
{t('framework.show_all_comments')}
</Button>
}
{commentId &&
<Button
cStyle="darkGrey"
style={{float: 'right'}}
onClick={viewAllComments}
>
{t('framework.show_all_comments')}
</Button>}
<Slot fill="embed" />
<TabContent show={activeTab === 'stream'}>
{ loggedIn ? userBox : null }
<Stream data={this.props.data} root={this.props.root} />
</TabContent>
<TabContent show={activeTab === 'profile'}>
<ProfileContainer />
</TabContent>
<TabContent show={activeTab === 'config'}>
{ loggedIn ? userBox : null }
<ConfigureStreamContainer />
</TabContent>
</div>
@@ -79,5 +74,5 @@ Embed.propTypes = {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object
}).isRequired,
}).isRequired
};
@@ -1,20 +1,18 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
import Comment from '../containers/Comment';
import SuspendedAccount from './SuspendedAccount';
import RestrictedMessageBox
from 'coral-framework/components/RestrictedMessageBox';
import Slot from 'coral-framework/components/Slot';
import InfoBox from 'coral-plugin-infobox/InfoBox';
import {can} from 'coral-framework/services/perms';
import {ModerationLink} from 'coral-plugin-moderation';
import CommentBox from 'coral-plugin-commentbox/CommentBox';
import QuestionBox from 'coral-plugin-questionbox/QuestionBox';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import SuspendedAccount from './SuspendedAccount';
import RestrictedMessageBox
from 'coral-framework/components/RestrictedMessageBox';
import {can} from 'coral-framework/services/perms';
import ChangeUsernameContainer
from 'coral-sign-in/containers/ChangeUsernameContainer';
import t, {timeago} from 'coral-framework/services/i18n';
class Stream extends React.Component {
@@ -52,7 +50,10 @@ class Stream extends React.Component {
: comment;
const banned = user && user.status === 'BANNED';
const temporarilySuspended = user && user.suspension.until && new Date(user.suspension.until) > new Date();
const temporarilySuspended =
user &&
user.suspension.until &&
new Date(user.suspension.until) > new Date();
const hasOlderComments = !!(asset &&
asset.lastComment &&
@@ -63,10 +64,15 @@ class Stream extends React.Component {
? asset.comments[0].created_at
: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString();
const commentIsIgnored = (comment) => {
return me && me.ignoredUsers && me.ignoredUsers.find((u) => u.id === comment.user.id);
return (
me &&
me.ignoredUsers &&
me.ignoredUsers.find((u) => u.id === comment.user.id)
);
};
return (
<div id="stream">
<Slot fill="stream" />
{open
? <div id="commentBox">
<InfoBox
@@ -77,52 +83,44 @@ class Stream extends React.Component {
content={asset.settings.questionBoxContent}
enable={asset.settings.questionBoxEnable}
/>
{!banned && temporarilySuspended &&
{!banned &&
temporarilySuspended &&
<RestrictedMessageBox>
{
t('sign_in.temporarily_suspended',
this.props.root.settings.organizationName,
timeago(user.suspension.until),
)
}
</RestrictedMessageBox>
}
{t(
'stream.temporarily_suspended',
this.props.root.settings.organizationName,
timeago(user.suspension.until)
)}
</RestrictedMessageBox>}
{banned &&
<SuspendedAccount
canEditName={user && user.canEditName}
editName={editName}
/>
}
{loggedIn && !banned && !temporarilySuspended &&
/>}
{loggedIn &&
!banned &&
!temporarilySuspended &&
<CommentBox
addNotification={this.props.addNotification}
postComment={this.props.postComment}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
setCommentCountCache={this.props.setCommentCountCache}
commentCountCache={commentCountCache}
assetId={asset.id}
premod={asset.settings.moderation}
isReply={false}
authorId={user.id}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
}
addNotification={this.props.addNotification}
postComment={this.props.postComment}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
setCommentCountCache={this.props.setCommentCountCache}
commentCountCache={commentCountCache}
assetId={asset.id}
premod={asset.settings.moderation}
isReply={false}
authorId={user.id}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>}
</div>
: <p>{asset.settings.closedMessage}</p>}
{!loggedIn &&
<Button
id="coralSignInButton"
onClick={this.props.showSignInDialog}
full
>
{t('sign_in.sign_in_to_comment')}
</Button>}
{loggedIn &&
user &&
<ChangeUsernameContainer loggedIn={loggedIn} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={can(user, 'MODERATE_COMMENTS')} />}
<ModerationLink
assetId={asset.id}
isAdmin={can(user, 'MODERATE_COMMENTS')}
/>}
{/* the highlightedComment is isolated after the user followed a permalink */}
{highlightedComment
@@ -160,41 +158,38 @@ class Stream extends React.Component {
setCommentCountCache={this.props.setCommentCountCache}
/>
<div className="embed__stream">
{comments.map(
(comment) => {
return (commentIsIgnored(comment)
? <IgnoredCommentTombstone key={comment.id} />
: <Comment
data={this.props.data}
root={this.props.root}
disableReply={!open}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
addNotification={addNotification}
depth={0}
postComment={postComment}
asset={asset}
currentUser={user}
postFlag={postFlag}
postDontAgree={postDontAgree}
addCommentTag={addCommentTag}
removeCommentTag={removeCommentTag}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadMore}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment}
pluginProps={pluginProps}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
editComment={this.props.editComment}
/>
);
}
)}
{comments.map((comment) => {
return commentIsIgnored(comment)
? <IgnoredCommentTombstone key={comment.id} />
: <Comment
data={this.props.data}
root={this.props.root}
disableReply={!open}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
addNotification={addNotification}
depth={0}
postComment={postComment}
asset={asset}
currentUser={user}
postFlag={postFlag}
postDontAgree={postDontAgree}
addCommentTag={addCommentTag}
removeCommentTag={removeCommentTag}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadMore}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment}
pluginProps={pluginProps}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
editComment={this.props.editComment}
/>;
})}
</div>
<LoadMore
topLevel={true}
@@ -223,7 +218,7 @@ Stream.propTypes = {
ignoreUser: React.PropTypes.func,
// edit a comment, passed (id, asset_id, { body })
editComment: React.PropTypes.func,
editComment: React.PropTypes.func
};
export default Stream;
+4 -4
View File
@@ -8,16 +8,16 @@ import './graphql';
import {addExternalConfig} from 'coral-embed-stream/src/actions/config';
import reducers from './reducers';
import localStore, {injectReducers} from 'coral-framework/services/store';
import store, {injectReducers} from 'coral-framework/services/store';
import AppRouter from './AppRouter';
import {pym} from 'coral-framework';
import {loadPluginsTranslations} from 'coral-framework/helpers/plugins';
loadPluginsTranslations();
injectReducers(reducers);
const store = (window.opener && window.opener.coralStore) ? window.opener.coralStore : localStore;
// Don't run this in the popup.
if (store === localStore) {
if (!window.opener) {
store.dispatch(checkLogin());
pym.sendMessage('getConfig');
+4 -2
View File
@@ -170,6 +170,7 @@ hr {
display: flex;
align-items: center;
justify-content: center;
font-weight: 400;
}
.hidden {
@@ -240,6 +241,8 @@ hr {
.coral-plugin-commentcontent-text {
margin-bottom: 7px;
font-size: 16px;
font-weight: 100;
line-height: 1.3;
}
.coral-plugin-author-name-text {
@@ -319,7 +322,6 @@ button.comment__action-button[disabled],
.coral-plugin-pubdate-text {
display: inline-block;
margin-left: 5px;
}
.coral-plugin-permalinks-container {
@@ -367,7 +369,7 @@ button.comment__action-button[disabled],
.coral-plugin-flags-popup-radio-label {
margin:5px;
font-weight: 700;
font-weight: 400;
font-size: .9rem;
}
+1 -1
View File
@@ -1,5 +1,5 @@
import * as actions from '../constants/asset';
import coralApi from '../helpers/response';
import coralApi from '../helpers/request';
import {addNotification} from '../actions/notification';
import t from 'coral-framework/services/i18n';
+50 -31
View File
@@ -1,13 +1,13 @@
import {pym} from 'coral-framework';
import * as Storage from '../helpers/storage';
import * as actions from '../constants/auth';
import coralApi, {base} from '../helpers/response';
import jwtDecode from 'jwt-decode';
import {pym} from 'coral-framework';
import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as Storage from '../helpers/storage';
import coralApi, {base} from '../helpers/request';
import t from 'coral-framework/services/i18n';
// Dialog Actions
export const showSignInDialog = () => (dispatch) => {
export const showSignInDialog = () => (dispatch, getState) => {
const signInPopUp = window.open(
'/embed/stream/login',
'Login',
@@ -19,6 +19,10 @@ export const showSignInDialog = () => (dispatch) => {
let loaded = false;
signInPopUp.onload = () => {
loaded = true;
// Fire some actions inside the popups reducer, to initialize required state.
const required = getState().asset.toJS().settings.requireEmailConfirmation;
signInPopUp.coralStore.dispatch(setRequireEmailVerification(required));
};
// Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
@@ -94,6 +98,11 @@ export const cleanState = () => ({
type: actions.CLEAN_STATE
});
export const setRequireEmailVerification = (required) => ({
type: actions.SET_REQUIRE_EMAIL_VERIFICATION,
required,
});
// Sign In Actions
const signInRequest = () => ({
@@ -119,26 +128,31 @@ export const handleAuthToken = (token) => (dispatch) => {
// SIGN IN
//==============================================================================
export const fetchSignIn = (formData) => (dispatch) => {
dispatch(signInRequest());
return coralApi('/auth/local', {method: 'POST', body: formData})
.then(({token}) => {
dispatch(handleAuthToken(token));
dispatch(hideSignInDialog());
})
.catch((error) => {
if (error.metadata) {
export const fetchSignIn = (formData) => {
return (dispatch) => {
dispatch(signInRequest());
// the user might not have a valid email. prompt the user user re-request the confirmation email
dispatch(
signInFailure(t('error.email_not_verified', error.metadata))
);
} else {
return coralApi('/auth/local', {method: 'POST', body: formData})
.then(({token}) => {
if (!bowser.safari && !bowser.ios) {
dispatch(handleAuthToken(token));
}
dispatch(hideSignInDialog());
})
.catch((error) => {
if (error.metadata) {
// invalid credentials
dispatch(signInFailure(t('error.email_password')));
}
});
// the user might not have a valid email. prompt the user user re-request the confirmation email
dispatch(
signInFailure(t('error.email_not_verified', error.metadata))
);
} else {
// invalid credentials
dispatch(signInFailure(t('error.email_password')));
}
});
};
};
//==============================================================================
@@ -185,7 +199,7 @@ export const fetchSignUpFacebook = () => (dispatch) => {
);
};
export const facebookCallback = (err, data) => (dispatch, getState) => {
export const facebookCallback = (err, data) => (dispatch) => {
if (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -194,10 +208,6 @@ export const facebookCallback = (err, data) => (dispatch, getState) => {
dispatch(handleAuthToken(data.token));
dispatch(signInFacebookSuccess(data.user));
dispatch(hideSignInDialog());
const {user: {canEditName, status}} = getState().auth.toJS();
if (canEditName && status !== 'BANNED') {
dispatch(showCreateUsernameDialog());
}
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -267,7 +277,9 @@ export const fetchForgotPassword = (email) => (dispatch) => {
export const logout = () => (dispatch) => {
return coralApi('/auth', {method: 'DELETE'}).then(() => {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
dispatch({type: actions.LOGOUT});
});
};
@@ -290,11 +302,18 @@ export const checkLogin = () => (dispatch) => {
coralApi('/auth')
.then((result) => {
if (!result.user) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
throw new Error('Not logged in');
}
dispatch(checkLoginSuccess(result.user));
// Display create username dialog if necessary.
if (result.user.canEditName && result.user.status !== 'BANNED') {
dispatch(showCreateUsernameDialog());
}
})
.catch((error) => {
console.error(error);
+1 -1
View File
@@ -1,5 +1,5 @@
import {addNotification} from '../actions/notification';
import coralApi from '../helpers/response';
import coralApi from '../helpers/request';
import * as actions from '../constants/auth';
import t from 'coral-framework/services/i18n';
+3
View File
@@ -46,3 +46,6 @@ export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST';
export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS';
export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE';
export const UPDATE_USERNAME = 'UPDATE_USERNAME';
export const SET_REQUIRE_EMAIL_VERIFICATION = 'SET_REQUIRE_EMAIL_VERIFICATION';
+5 -1
View File
@@ -6,6 +6,7 @@ import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import plugins from 'pluginsConfig';
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
import {loadTranslations} from 'coral-framework/services/i18n';
export const pluginReducers = merge(
...plugins
@@ -88,9 +89,12 @@ export function getGraphQLExtensions() {
.filter((o) => o);
}
export function getTranslations() {
function getTranslations() {
return plugins
.map((o) => o.module.translations)
.filter((o) => o);
}
export function loadPluginsTranslations() {
getTranslations().forEach((t) => loadTranslations(t));
}
@@ -1,22 +1,26 @@
import bowser from 'bowser';
import * as Storage from './storage';
import merge from 'lodash/merge';
const buildOptions = (inputOptions = {}) => {
const defaultOptions = {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${Storage.getItem('token')}`,
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
credentials: 'same-origin'
};
let options = Object.assign({}, defaultOptions, inputOptions);
options.headers = Object.assign(
{},
defaultOptions.headers,
inputOptions.headers
);
let options = merge({}, defaultOptions, inputOptions);
if (!bowser.safari && !bowser.ios) {
let authorization = Storage.getItem('token');
if (authorization) {
options.headers.Authorization = `Bearer ${authorization}`;
}
}
if (options.method.toLowerCase() !== 'get') {
options.body = JSON.stringify(options.body);
+5 -1
View File
@@ -16,7 +16,8 @@ const initialState = Map({
emailVerificationLoading: false,
emailVerificationSuccess: false,
successSignUp: false,
fromSignUp: false
fromSignUp: false,
requireEmailConfirmation: false,
});
const purge = (user) => {
@@ -142,6 +143,9 @@ export default function auth (state = initialState, action) {
return state
.set('emailVerificationSuccess', true)
.set('emailVerificationLoading', false);
case actions.SET_REQUIRE_EMAIL_VERIFICATION:
return state
.set('requireEmailConfirmation', action.required);
default :
return state;
}
-10
View File
@@ -6,7 +6,6 @@ import merge from 'lodash/merge';
import esTA from '../../../node_modules/timeago.js/locales/es';
import en from '../../../locales/en.yml';
import es from '../../../locales/es.yml';
import * as plugins from '../helpers/plugins';
// Translations are happening at https://www.transifex.com/the-coral-project/talk-1/dashboard/.
@@ -15,7 +14,6 @@ const translations = {...en, ...es};
let lang;
let timeagoInstance;
let loadedPluginsTranslations = false;
function setLocale(locale) {
try {
@@ -45,10 +43,6 @@ function init() {
timeagoInstance = ta();
}
function loadPluginsTranslations() {
plugins.getTranslations().forEach((t) => loadTranslations(t));
}
export function loadTranslations(newTranslations) {
merge(translations, newTranslations);
}
@@ -67,10 +61,6 @@ export function timeago(time) {
* any extra parameters are optional and replace a variable marked by {0}, {1}, etc in the translation.
*/
export function t(key, ...replacements) {
if (!loadedPluginsTranslations) {
loadPluginsTranslations();
loadedPluginsTranslations = true;
}
const fullKey = `${lang}.${key}`;
if (has(translations, fullKey)) {
let translation = get(translations, fullKey);
+6 -1
View File
@@ -1,5 +1,6 @@
import {createNetworkInterface} from 'apollo-client';
import * as Storage from '../helpers/storage';
import bowser from 'bowser';
//==============================================================================
// NETWORK INTERFACE
@@ -21,7 +22,11 @@ networkInterface.use([{
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`;
if (!bowser.safari && !bowser.ios) {
req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`;
}
next();
}
}]);
+3 -4
View File
@@ -13,10 +13,10 @@
}
.assetURL {
font-size: 16px;
color: black;
text-decoration: none;
font-weight: bold;
font-size: 12px;
color: #2c3e50;
}
.commentBody {
@@ -32,7 +32,7 @@
}
li {
margin-bottom: 10px;
margin-bottom: 5px;
&:nth-child(1) {
color: #5394D7;
@@ -46,7 +46,6 @@
i {
margin-right: 5px;
font-size: 15px;
vertical-align: bottom;
}
a:hover {
@@ -6,7 +6,7 @@ import t from 'coral-framework/services/i18n';
export default ({showSignInDialog}) => (
<div className={styles.message}>
<div>
<a onClick={showSignInDialog}>{t('sign_in')}</a> {t('toAccess')}
<a onClick={showSignInDialog}>{t('settings.sign_in')}</a> {t('settings.to_access')}
</div>
<div>
{t('from_settings_page')}
@@ -1,54 +0,0 @@
import React from 'react';
import TextField from 'coral-ui/components/TextField';
import Button from 'coral-ui/components/Button';
import {Dialog, Alert} from 'coral-ui';
import FakeComment from './FakeComment';
import styles from './styles.css';
import t from 'coral-framework/services/i18n';
const CreateUsernameDialog = ({open, handleClose, formData, handleSubmitUsername, handleChange, ...props}) => {
return (
<Dialog
className={styles.dialogusername}
id="createUsernameDialog"
open={open}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h1>
{t('createdisplay.write_your_username')}
</h1>
</div>
<div>
<p className={styles.yourusername}>{t('createdisplay.your_username')}</p>
<FakeComment
className={styles.fakeComment}
username={formData.username}
created_at={Date.now()}
body={t('createdisplay.fake_comment_body')}
/>
<p className={styles.ifyoudont}>{t('createdisplay.if_you_dont_change_your_name')}</p>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form id="saveUsername" onSubmit={handleSubmitUsername}>
{ props.errors.username && <span className={styles.hint}> {t('createdisplay.special_characters')} </span> }
<div className={styles.saveusername}>
<TextField
id="username"
style={{fontSize: 16}}
type="string"
label={t('createdisplay.username')}
value={formData.username}
onChange={handleChange}
/>
<Button id="save" type="submit" className={styles.saveButton}>{t('createdisplay.save')}</Button>
</div>
</form>
</div>
</div>
</Dialog>
);
};
export default CreateUsernameDialog;
@@ -1,62 +0,0 @@
import React from 'react';
import styles from 'coral-embed-stream/src/components/Comment.css';
import AuthorName from 'coral-plugin-author-name/AuthorName';
import Content from 'coral-plugin-commentcontent/CommentContent';
import PubDate from 'coral-plugin-pubdate/PubDate';
import {ReplyButton} from 'coral-plugin-replies';
import t from 'coral-framework/services/i18n';
class FakeComment extends React.Component {
constructor (props) {
super(props);
}
render () {
const {username, created_at, body} = this.props;
return (
<div
className={`comment ${styles.Comment}`}
style={{marginLeft: 0 * 30}}>
<hr aria-hidden={true} />
<AuthorName
author={{'name': username}}/>
<PubDate created_at={created_at} />
<Content body={body} />
<div className="commentActionsLeft comment__action-container">
<div className={`${'coral-plugin-likes'}-container`}>
<button className={'coral-plugin-likes-button'}>
<span className={'coral-plugin-likes-button-text'}>{t('like')}</span>
<i className={`${'coral-plugin-likes'}-icon material-icons`}
aria-hidden={true}>thumb_up</i>
</button>
</div>
<ReplyButton
onClick={() => {}}
parentCommentId={'commentID'}
currentUserId={{}}
banned={false}
/>
</div>
<div className="commentActionsRight comment__action-container">
<div className="coral-plugin-permalinks-container">
<button className="coral-plugin-permalinks-button">
<span className={`comment__action-button comment__action-button--nowrap ${'coral-plugin-flags'}-button-text`}>{t('permalink')}</span>
<i className="coral-plugin-permalinks-icon material-icons" aria-hidden={true}>link</i>
</button>
</div>
<div className={`${'coral-plugin-flags'}-container`}>
<button className={`${'coral-plugin-flags'}-button`}>
<span className={`comment__action-button comment__action-button--nowrap ${'coral-plugin-flags'}-button-text`}>{t('report')}</span>
<i className={`${'coral-plugin-flags'}-icon material-icons`}
aria-hidden={true}>flag</i>
</button>
</div>
</div>
</div>
);
}
}
export default FakeComment;
@@ -1,59 +0,0 @@
import React from 'react';
import styles from './styles.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
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>{t('sign_in.recover_password')}</h1>
</div>
<form onSubmit={this.handleSubmit}>
<div className={styles.textField}>
<label htmlFor="email">{t('sign_in.email')}</label>
<input
ref={(input) => this.emailInput = input}
type="text"
style={{fontSize: 16}}
id="email"
name="email" />
</div>
<Button type="submit" cStyle="black" className={styles.signInButton} full>
{t('sign_in.recover_password')}
</Button>
{
passwordRequestSuccess
? <p className={styles.passwordRequestSuccess}>{passwordRequestSuccess}</p>
: null
}
{
passwordRequestFailure
? <p className={styles.passwordRequestFailure}>{passwordRequestFailure}</p>
: null
}
</form>
<div className={styles.footer}>
<span>{t('sign_in.need_an_account')} <a onClick={() => changeView('SIGNUP')}>{t('sign_in.register')}</a></span>
<span>{t('sign_in.already_have_an_account')} <a onClick={() => changeView('SIGNIN')}>{t('sign_in.sign_in')}</a></span>
</div>
</div>
);
}
}
export default ForgotContent;
@@ -1,21 +0,0 @@
import React from 'react';
import {Dialog} from 'coral-ui';
import styles from './styles.css';
import SignInContent from './SignInContent';
import SignUpContent from './SignUpContent';
import ForgotContent from './ForgotContent';
const SignDialog = ({open, view, handleClose, ...props}) => (
<Dialog
className={styles.dialog}
id="signInDialog"
open={open}>
<span className={styles.close} onClick={handleClose}>×</span>
{view === 'SIGNIN' && <SignInContent {...props} />}
{view === 'SIGNUP' && <SignUpContent {...props} />}
{view === 'FORGOT' && <ForgotContent {...props} />}
</Dialog>
);
export default SignDialog;
@@ -1,112 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, TextField, Spinner, Success, Alert} from 'coral-ui';
import styles from './styles.css';
import t from 'coral-framework/services/i18n';
const SignInContent = ({
handleChange,
handleChangeEmail,
emailToBeResent,
handleResendVerification,
emailVerificationLoading,
emailVerificationSuccess,
formData,
changeView,
handleSignIn,
auth,
fetchSignInFacebook
}) => {
return (
<div className="coral-sign-in">
<div className={`${styles.header} header`}>
<h1>
{auth.emailVerificationFailure ? t('sign_in.email_verify_cta') : t('sign_in.sign_in_to_join')}
</h1>
</div>
{ auth.error && <Alert>{auth.error}</Alert> }
{
auth.emailVerificationFailure
? <form onSubmit={handleResendVerification}>
<p>{t('sign_in.request_new_verify_email')}</p>
<TextField
id="confirm-email"
type="email"
label={t('sign_in.email')}
value={emailToBeResent}
onChange={handleChangeEmail} />
<Button id='resendConfirmEmail' type='submit' cStyle='black' full>Send Email</Button>
{emailVerificationLoading && <Spinner />}
{emailVerificationSuccess && <Success />}
</form>
: <div>
<div className={`${styles.socialConnections} social-connections`}>
<Button cStyle="facebook" onClick={fetchSignInFacebook} full>
{t('sign_in.facebook_sign_in')}
</Button>
</div>
<div className={styles.separator}>
<h1>
{t('sign_in.or')}
</h1>
</div>
<form onSubmit={handleSignIn}>
<TextField
id="email"
type="email"
label={t('sign_in.email')}
value={formData.email}
style={{fontSize: 16}}
onChange={handleChange}
/>
<TextField
id="password"
type="password"
label={t('sign_in.password')}
value={formData.password}
style={{fontSize: 16}}
onChange={handleChange}
/>
<div className={styles.action}>
{
!auth.isLoading ?
<Button id='coralLogInButton' type="submit" cStyle="black" className={styles.signInButton} full>
{t('sign_in.sign_in')}
</Button>
:
<Spinner />
}
</div>
</form>
</div>
}
<div className={`${styles.footer} footer`}>
<span><a onClick={() => changeView('FORGOT')}>{t('sign_in.forgot_your_pass')}</a></span>
<span>
{t('sign_in.need_an_account')}
<a onClick={() => changeView('SIGNUP')} id='coralRegister'>
{t('sign_in.register')}
</a>
</span>
</div>
</div>
);
};
SignInContent.propTypes = {
auth: PropTypes.shape({
isLoading: PropTypes.bool.isRequired,
error: PropTypes.string,
emailVerificationFailure: PropTypes.bool
}).isRequired,
fetchSignInFacebook: PropTypes.func.isRequired,
handleSignIn: PropTypes.func.isRequired,
changeView: PropTypes.func.isRequired,
emailVerificationLoading: PropTypes.bool.isRequired,
emailVerificationSuccess: PropTypes.bool.isRequired,
handleResendVerification: PropTypes.func.isRequired,
handleChangeEmail: PropTypes.func.isRequired,
emailToBeResent: PropTypes.string.isRequired
};
export default SignInContent;
@@ -1,151 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, TextField, Spinner, Success, Alert} from 'coral-ui';
import styles from './styles.css';
import t from 'coral-framework/services/i18n';
class SignUpContent extends React.Component {
constructor (props) {
super(props);
this.successfulSignup = false;
}
static propTypes = {
emailVerificationEnabled: PropTypes.bool.isRequired,
fetchSignUpFacebook: PropTypes.func.isRequired,
changeView: PropTypes.func.isRequired,
handleSignUp: PropTypes.func.isRequired,
showErrors: PropTypes.bool,
errors: PropTypes.shape({
email: PropTypes.string,
username: PropTypes.string,
password: PropTypes.string,
confirmPassword: PropTypes.string,
}),
formData: PropTypes.shape({
email: PropTypes.string,
username: PropTypes.string,
password: PropTypes.string,
confirmPassword: PropTypes.string
})
}
render () {
const {
handleChange,
formData,
emailVerificationEnabled,
auth,
errors,
showErrors,
changeView,
handleSignUp,
fetchSignUpFacebook} = this.props;
const beforeSignup = !auth.isLoading && !auth.successSignUp;
const successfulSignup = !auth.isLoading && auth.successSignUp;
// the first time we render a successfulSignup, trigger a timer
if ((this.successfulSignup ^ successfulSignup) && !emailVerificationEnabled) {
setTimeout(() => {
changeView('SIGNIN');
}, 1000);
this.successfulSignup = true;
}
return (
<div>
<div className={styles.header}>
<h1>
{t('sign_in.sign_up')}
</h1>
</div>
{ auth.error && <Alert>{auth.error}</Alert> }
{ beforeSignup &&
<div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={fetchSignUpFacebook} full>
{t('sign_in.facebook_sign_up')}
</Button>
</div>
<div className={styles.separator}>
<h1>
{t('sign_in.or')}
</h1>
</div>
<form onSubmit={handleSignUp}>
<TextField
id="email"
type="email"
label={t('sign_in.email')}
value={formData.email}
style={{fontSize: 16}}
showErrors={showErrors}
errorMsg={errors.email}
onChange={handleChange}
/>
<TextField
id="username"
type="text"
label={t('sign_in.username')}
value={formData.username}
showErrors={showErrors}
style={{fontSize: 16}}
errorMsg={errors.username}
onChange={handleChange}
/>
<TextField
id="password"
type="password"
label={t('sign_in.password')}
value={formData.password}
showErrors={showErrors}
style={{fontSize: 16}}
errorMsg={errors.password}
onChange={handleChange}
minLength="8"
/>
{ errors.password && <span className={styles.hint}> Password must be at least 8 characters. </span> }
<TextField
id="confirmPassword"
type="password"
label={t('sign_in.confirm_password')}
value={formData.confirmPassword}
style={{fontSize: 16}}
showErrors={showErrors}
errorMsg={errors.confirmPassword}
onChange={handleChange}
minLength="8"
/>
<div className={styles.action}>
<Button type="submit" cStyle="black" id='coralSignUpButton' className={styles.signInButton} full>
{t('sign_in.sign_up')}
</Button>
{ auth.isLoading && <Spinner /> }
</div>
</form>
</div>
}
{
successfulSignup &&
<div>
<Success />
{
emailVerificationEnabled &&
<p>{t('sign_in.verify_email')}<br /><br />{t('sign_in.verify_email2')}</p>
}
</div>
}
<div className={styles.footer}>
{t('sign_in.already_have_an_account')} <a id="coralSignInViewTrigger" onClick={() => changeView('SIGNIN')}>
{t('sign_in.sign_in')}
</a>
</div>
</div>
);
}
}
export default SignUpContent;
@@ -1,13 +0,0 @@
import React from 'react';
import styles from './styles.css';
import t from 'coral-framework/services/i18n';
const UserBox = ({className, user, onLogout, onShowProfile}) => (
<div className={`${styles.userBox} ${className ? className : ''}`}>
{t('sign_in.logged_in_as')}
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
<a className={styles.logout} onClick={onLogout} id='logout'>{t('sign_in.logout')}</a>
</div>
);
export default UserBox;
-163
View File
@@ -1,163 +0,0 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 280px;
top: 10px;
}
.header {
margin-bottom: 20px;
}
.header h1, .separator h1{
text-align: center;
font-size: 1.2em;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.socialConnections {
margin-bottom: 20px;
}
.signInButton {
margin-top: 10px;
background-color: #2a2a2a;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
}
.close:hover {
color: #6b6b6b;
}
input.error{
border: solid 2px #f44336;
}
.errorMsg, .hint {
color: grey;
font-weight: 600;
padding: 3px 0 16px;
}
.userBox {
padding: 10px 0 20px;
letter-spacing: 0.1px;
}
.userBox a {
color: black;
font-weight: bold;
cursor: pointer;
margin: 0px;
margin-left: 4px;
padding-bottom: 2px;
}
.userBox .logout {
border-bottom: solid 1px black;
}
.attention {
display: inline-block;
width: 15px;
height: 15px;
background: #B71C1C;
color: #FFEBEE;
font-weight: bolder;
padding: 4px;
vertical-align: middle;
border-radius: 20px;
box-sizing: border-box;
font-size: 9px;
line-height: 7px;
text-align: center;
margin-right: 5px;
}
.action {
margin-top: 0px;
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral;
padding: 10px;
}
.emailConfirmDialog {
margin-top: 15px;
}
.confirmLabel {
display: block;
}
/* Change username Dialog*/
.dialogusername {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 400px;
top: 10px;
}
.yourusername {
display: block;
}
.example {
display: block;
}
.ifyoudont {
display: block;
margin-top: 15px;
}
.saveusername {
display: block;
width: 100%;
}
.savebutton {
display: inline;
background-color: rgb(105,105,105);
color: white;
}
.fakeComment {
display: block;
margin-bottom: 5px;
}
@@ -1,133 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import CreateUsernameDialog from '../components/CreateUsernameDialog';
import t from 'coral-framework/services/i18n';
import {
showCreateUsernameDialog,
hideCreateUsernameDialog,
invalidForm,
validForm,
createUsername
} from '../../coral-framework/actions/auth';
class ChangeUsernameContainer extends Component {
initialState = {
formData: {
username: '',
},
errors: {},
showErrors: false
};
constructor(props) {
super(props);
this.initialState.formData.username = props.user.username;
this.state = this.initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmitUsername = this.handleSubmitUsername.bind(this);
this.handleClose = this.handleClose.bind(this);
this.addError = this.addError.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
this.setState((state) => ({
...state,
formData: {
...state.formData,
[name]: value
}
}), () => {
this.validation(name, value);
});
}
addError(name, error) {
return this.setState((state) => ({
errors: {
...state.errors,
[name]: error
}
}));
}
validation(name, value) {
const {addError} = this;
if (!value.length) {
addError(name, t('createdisplay.required_field'));
} else if (!validate[name](value)) {
addError(name, errorMsj[name]);
} else {
const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line
// Removes Error
this.setState((state) => ({...state, errors}));
}
}
isCompleted() {
const {formData} = this.state;
return !Object.keys(formData).filter((prop) => !formData[prop].length).length;
}
displayErrors(show = true) {
this.setState({showErrors: show});
}
handleSubmitUsername(e) {
e.preventDefault();
const {errors} = this.state;
const {validForm, invalidForm} = this.props;
this.displayErrors();
if (this.isCompleted() && !Object.keys(errors).length) {
this.props.createUsername(this.props.user.id, this.state.formData);
validForm();
} else {
invalidForm(t('createdisplay.check_the_form'));
}
}
handleClose() {
this.props.hideCreateUsernameDialog();
}
render() {
const {loggedIn, auth} = this.props;
return (
<div>
<CreateUsernameDialog
open={auth.showCreateUsernameDialog}
handleClose={this.handleClose}
loggedIn={loggedIn}
handleSubmitUsername={this.handleSubmitUsername}
{...this}
{...this.state}
{...this.props}
/>
</div>
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = (dispatch) => ({
createUsername: (userid, formData) => dispatch(createUsername(userid, formData)),
showCreateUsernameDialog: () => dispatch(showCreateUsernameDialog()),
hideCreateUsernameDialog: () => dispatch(hideCreateUsernameDialog()),
invalidForm: (error) => dispatch(invalidForm(error)),
validForm: () => dispatch(validForm())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChangeUsernameContainer);
@@ -0,0 +1,6 @@
import React from 'react';
import Slot from 'coral-framework/components/Slot';
export const LoginContainer = () => (
<Slot fill="login"/>
);
@@ -1,219 +0,0 @@
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import SignDialog from '../components/SignDialog';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import {pym} from 'coral-framework';
import t from 'coral-framework/services/i18n';
import {
changeView,
fetchSignUp,
fetchSignIn,
hideSignInDialog,
fetchSignInFacebook,
fetchSignUpFacebook,
fetchForgotPassword,
requestConfirmEmail,
facebookCallback,
invalidForm,
validForm,
checkLogin
} from 'coral-framework/actions/auth';
class SignInContainer extends Component {
initialState = {
formData: {
email: '',
username: '',
password: '',
confirmPassword: ''
},
emailToBeResent: '',
errors: {},
showErrors: false
};
constructor(props) {
super(props);
this.state = this.initialState;
this.addError = this.addError.bind(this);
this.handleAuth = this.handleAuth.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.handleSignIn = this.handleSignIn.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleChangeEmail = this.handleChangeEmail.bind(this);
this.handleResendVerification = this.handleResendVerification.bind(this);
}
static propTypes = {
requireEmailConfirmation: PropTypes.bool.isRequired
};
componentWillMount() {
this.props.checkLogin();
}
componentDidMount() {
window.addEventListener('storage', this.handleAuth);
const {formData} = this.state;
const errors = Object.keys(formData).reduce((map, prop) => {
map[prop] = t('sign_in.required_field');
return map;
}, {});
this.setState({errors});
}
componentWillUnmount() {
window.removeEventListener('storage', this.handleAuth);
}
handleAuth(e) {
// Listening to FB changes
// FB localStorage key is 'auth'
const authCallback = this.props.facebookCallback;
if (e.key === 'auth') {
const {err, data} = JSON.parse(e.newValue);
authCallback(err, data);
}
}
handleChange(e) {
const {name, value} = e.target;
this.setState(
(state) => ({
...state,
formData: {
...state.formData,
[name]: value
}
}),
() => {
this.validation(name, value);
}
);
}
handleChangeEmail(e) {
const {value} = e.target;
this.setState({emailToBeResent: value});
}
handleResendVerification(e) {
e.preventDefault();
this.props
.requestConfirmEmail(
this.state.emailToBeResent,
pym.parentUrl || location.href
)
.then(() => {
setTimeout(() => {
// allow success UI to be shown for a second, and then close the modal
this.props.handleClose();
}, 2500);
});
}
addError(name, error) {
return this.setState((state) => ({
errors: {
...state.errors,
[name]: error
}
}));
}
validation(name, value) {
const {addError} = this;
const {formData} = this.state;
if (!value.length) {
addError(name, t('sign_in.required_field'));
} else if (
name === 'confirmPassword' &&
formData.confirmPassword !== formData.password
) {
addError('confirmPassword', t('sign_in.passwords_dont_match'));
} else if (!validate[name](value)) {
addError(name, errorMsj[name]);
} else {
const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line
// Removes Error
this.setState((state) => ({...state, errors}));
}
}
isCompleted() {
const {formData} = this.state;
return !Object.keys(formData).filter((prop) => !formData[prop].length).length;
}
displayErrors(show = true) {
this.setState({showErrors: show});
}
handleSignUp(e) {
e.preventDefault();
const {errors} = this.state;
const {fetchSignUp, validForm, invalidForm} = this.props;
this.displayErrors();
if (this.isCompleted() && !Object.keys(errors).length) {
fetchSignUp(this.state.formData, pym.parentUrl || location.href);
validForm();
} else {
invalidForm(t('sign_in.check_the_form'));
}
}
handleSignIn(e) {
e.preventDefault();
this.props.fetchSignIn(this.state.formData);
}
render() {
const {auth, requireEmailConfirmation} = this.props;
const {emailVerificationLoading, emailVerificationSuccess} = auth;
return (
<div>
<SignDialog
open={true}
view={auth.view}
emailVerificationEnabled={requireEmailConfirmation}
emailVerificationLoading={emailVerificationLoading}
emailVerificationSuccess={emailVerificationSuccess}
{...this}
{...this.state}
{...this.props}
/>
</div>
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = (dispatch) => ({
checkLogin: () => dispatch(checkLogin()),
facebookCallback: (err, data) => dispatch(facebookCallback(err, data)),
fetchSignUp: (formData, url) => dispatch(fetchSignUp(formData, url)),
fetchSignIn: (formData) => dispatch(fetchSignIn(formData)),
fetchSignInFacebook: () => dispatch(fetchSignInFacebook()),
fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()),
fetchForgotPassword: (formData) => dispatch(fetchForgotPassword(formData)),
requestConfirmEmail: (email, url) =>
dispatch(requestConfirmEmail(email, url)),
changeView: (view) => dispatch(changeView(view)),
handleClose: () => dispatch(hideSignInDialog()),
invalidForm: (error) => dispatch(invalidForm(error)),
validForm: () => dispatch(validForm())
});
export default connect(mapStateToProps, mapDispatchToProps)(SignInContainer);
+1
View File
@@ -18,6 +18,7 @@
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
font-size: 14px;
}
input.error{