Refactor talk-plugin-auth part 1

This commit is contained in:
Chi Vinh Le
2018-02-09 20:12:02 +01:00
parent 1774f98519
commit f4fc8915e3
36 changed files with 413 additions and 955 deletions
@@ -12,3 +12,12 @@
font-weight: normal;
text-decoration: none;
}
.recaptcha {
margin-top: 16px;
margin-bottom: 6px;
}
.signInButton {
margin-top: 10px;
}
+10 -11
View File
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import styles from './SignIn.css';
import { Button, TextField, Alert } from 'coral-ui';
import cn from 'classnames';
import Recaptcha from 'coral-framework/components/Recaptcha';
class SignIn extends React.Component {
@@ -27,9 +28,6 @@ class SignIn extends React.Component {
handleRecaptchaRef = ref => {
this.recaptcha = ref;
setTimeout(() => {
console.log(ref);
}, 1000)
};
render() {
@@ -50,9 +48,16 @@ class SignIn extends React.Component {
onChange={this.handlePasswordChange}
type="password"
/>
<div style={{ height: 10 }} />
{requireRecaptcha && (
<div className={styles.recaptcha}>
<Recaptcha
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
/>
</div>
)}
<Button
className="talk-admin-login-sign-in-button"
className={cn(styles.signInButton, 'talk-admin-login-sign-in-button')}
type="submit"
cStyle="black"
full
@@ -69,12 +74,6 @@ class SignIn extends React.Component {
Request a new one.
</a>
</p>
{requireRecaptcha && (
<Recaptcha
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
/>
)}
</form>
);
}
@@ -1,4 +0,0 @@
import React from 'react';
import Slot from 'coral-framework/components/Slot';
export const LoginContainer = () => <Slot fill="login" />;
@@ -31,6 +31,8 @@ class Recaptcha extends React.Component {
theme={this.props.theme}
onloadCallback={this.props.onLoad}
verifyCallback={this.props.onVerify}
size={this.props.size}
className={this.props.className}
/>
);
}
@@ -38,7 +40,8 @@ class Recaptcha extends React.Component {
Recaptcha.defaultProps = {
render: 'explicit',
theme: 'dark',
theme: 'light',
size: 'normal',
};
Recaptcha.propTypes = {
@@ -46,7 +49,8 @@ Recaptcha.propTypes = {
onVerify: PropTypes.func.isRequired,
theme: PropTypes.string,
render: PropTypes.string,
sitekey: PropTypes.string.isRequired,
size: PropTypes.string,
className: PropTypes.string,
};
export default Recaptcha;
+5 -3
View File
@@ -6,7 +6,7 @@ import { translateError } from '../utils';
import { t } from '../services/i18n';
/**
* WithSignIn provides properties `signIn`, `loading` and `errorMessage`, `requireRecaptcha`.
* WithSignIn provides properties `signIn`, `loading`, `errorMessage`, `requireRecaptcha`, 'success'.
*/
export default hoistStatics(WrappedComponent => {
class WithSignIn extends React.Component {
@@ -18,6 +18,7 @@ export default hoistStatics(WrappedComponent => {
state = {
error: null,
loading: false,
success: false,
requireRecaptcha: false,
};
@@ -39,14 +40,14 @@ export default hoistStatics(WrappedComponent => {
rest('/auth/local', params)
.then(({ user, token }) => {
this.setState({ loading: false, error: null });
this.setState({ success: true, loading: false, error: null });
store.dispatch(handleSuccessfulLogin(user, token));
})
.catch(error => {
if (!error.status || error.status !== 401) {
console.error(error);
}
const changeSet = { loading: false, error };
const changeSet = { success: false, loading: false, error };
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
changeSet.requireRecaptcha = !!this.context.store.getState().config
.static.TALK_RECAPTCHA_PUBLIC;
@@ -72,6 +73,7 @@ export default hoistStatics(WrappedComponent => {
loading={this.state.loading}
errorMessage={this.getErrorMessage()}
requireRecaptcha={this.state.requireRecaptcha}
success={this.state.success}
/>
);
}
+2 -1
View File
@@ -1,8 +1,9 @@
import React from 'react';
import Slot from 'coral-framework/components/Slot';
class Main extends React.Component {
render() {
return <div>Hello World</div>;
return <Slot fill="login" />;
}
}
@@ -1,38 +0,0 @@
.root {
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
padding: 8px 0px 10px 0px;
position: relative;
}
.footer {
display: flex;
justify-content: space-between;
}
.button {
color: #2a2a2a;
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
vertical-align: middle;
&:hover {
color: #767676;
cursor: pointer;
}
}
.icon {
font-size: 12px;
padding: 0 2px 0 5px;
vertical-align: middle;
}
.authorName {
margin-right: 5px;
font-weight: bold;
}
@@ -1,37 +0,0 @@
import React from 'react';
import t from 'coral-framework/services/i18n';
import ReplyButton from 'coral-embed-stream/src/tabs/stream/components/ReplyButton';
import styles from './FakeComment.css';
import { Icon } from 'plugin-api/beta/client/components/ui';
import { CommentTimestamp } from 'plugin-api/beta/client/components';
export const FakeComment = ({ username, created_at, body }) => (
<div className={styles.root}>
<span className={styles.authorName}>{username}</span>
<CommentTimestamp created_at={created_at} />
<div className={styles.body}>{body}</div>
<div className={styles.footer}>
<div>
<button className={styles.button}>
<span className={styles.label}>{t('like')}</span>
<Icon name="thumb_up" className={styles.icon} />
</button>
<ReplyButton
onClick={() => {}}
parentCommentId={'commentID'}
currentUserId={{}}
/>
</div>
<div>
<button className={styles.button}>
<span className={styles.label}>{t('permalink')}</span>
<Icon name="link" className={styles.icon} />
</button>
<button className={styles.button}>
<span className={styles.label}>{t('report')}</span>
<Icon name="flag" className={styles.icon} />
</button>
</div>
</div>
</div>
);
@@ -1,81 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './styles.css';
import { Button, TextField } from 'plugin-api/beta/client/components/ui';
import t from 'coral-framework/services/i18n';
class ForgotContent extends React.Component {
state = { value: '' };
handleSubmit = e => {
e.preventDefault();
this.props.fetchForgotPassword(this.state.value);
};
handleChangeEmail = e => {
const { value } = e.target;
this.setState({ 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}>
<TextField
type="email"
style={{ fontSize: 16 }}
id="email"
name="email"
label={t('sign_in.email')}
onChange={this.handleChangeEmail}
value={this.state.value}
/>
</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>
);
}
}
ForgotContent.propTypes = {
auth: PropTypes.object,
changeView: PropTypes.func,
fetchForgotPassword: PropTypes.func,
};
export default ForgotContent;
@@ -1,15 +0,0 @@
.header {
margin-bottom: 20px;
text-align: center;
font-size: 1.2em;
}
.notVerified {
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
border: solid 1px #dddd00;
background: #FFFF9C;
color: #777700;
}
@@ -1,54 +0,0 @@
import React from 'react';
import {
Button,
Spinner,
Success,
Alert,
} from 'plugin-api/beta/client/components/ui';
import PropTypes from 'prop-types';
import styles from './ResendVerification.css';
import t from 'coral-framework/services/i18n';
class ResendVerification extends React.Component {
render() {
const { resendVerification, error, loading, success, email } = this.props;
return (
<div className="talk-resend-verification">
<h1 className={styles.header}>{t('sign_in.email_verify_cta')}</h1>
{error && (
<Alert>
{error.translation_key
? t(`error.${error.translation_key}`)
: error.toString()}
</Alert>
)}
<div className={styles.notVerified}>
{t('error.email_not_verified', email)}
</div>
<div>
<Button
id="resendConfirmEmail"
cStyle="black"
onClick={resendVerification}
full
>
{t('sign_in.request_new_verify_email')}
</Button>
{loading && <Spinner />}
{success && <Success />}
</div>
</div>
);
}
}
ResendVerification.propTypes = {
resendVerification: PropTypes.bool.isRequired,
error: PropTypes.object,
loading: PropTypes.bool,
success: PropTypes.bool,
email: PropTypes.string.isRequired,
};
export default ResendVerification;
@@ -1,32 +0,0 @@
import React from 'react';
import { Dialog } from 'plugin-api/beta/client/components/ui';
import styles from './styles.css';
import SignInContent from './SignInContent';
import SignUpContent from './SignUpContent';
import ForgotContent from './ForgotContent';
import ResendVerification from './ResendVerification';
const SignDialog = ({ open, view, resetSignInDialog, ...props }) => (
<Dialog className={styles.dialog} id="signInDialog" open={open}>
{view !== 'SIGNIN' && (
<span className={styles.close} onClick={resetSignInDialog}>
×
</span>
)}
{view === 'SIGNIN' && <SignInContent {...props} />}
{view === 'SIGNUP' && <SignUpContent {...props} />}
{view === 'FORGOT' && <ForgotContent {...props} />}
{view === 'RESEND_VERIFICATION' && (
<ResendVerification
resendVerification={props.resendVerification}
error={props.auth.emailVerificationFailure}
success={props.auth.emailVerificationSuccess}
loading={props.auth.emailVerificationLoading}
email={props.auth.email}
/>
)}
</Dialog>
);
export default SignDialog;
@@ -1,202 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import SignDialog from './SignDialog';
import { bindActionCreators } from 'redux';
import t from 'coral-framework/services/i18n';
import errorMsj from 'coral-framework/helpers/error';
import validate from 'coral-framework/helpers/validate';
import {
changeView,
fetchSignUp,
fetchSignIn,
hideSignInDialog,
fetchSignInFacebook,
fetchSignUpFacebook,
fetchForgotPassword,
requestConfirmEmail,
resetSignInDialog,
facebookCallback,
invalidForm,
validForm,
} from 'coral-embed-stream/src/actions/login';
class SignInContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
formData: {
email: '',
username: '',
password: '',
confirmPassword: '',
},
errors: {},
showErrors: false,
};
}
componentDidMount() {
this.listenToStorageChanges();
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() {
this.unlisten();
}
listenToStorageChanges() {
window.addEventListener('storage', this.handleAuth);
}
unlisten() {
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);
this.unlisten();
localStorage.removeItem('auth');
}
};
handleChange = e => {
const { name, value } = e.target;
this.setState(
state => ({
...state,
formData: {
...state.formData,
[name]: value,
},
}),
() => {
this.validation(name, value);
}
);
};
resendVerification = () => {
this.props.requestConfirmEmail(this.props.auth.email).then(() => {
setTimeout(() => {
// allow success UI to be shown for a second, and then close the modal
this.props.resetSignInDialog();
}, 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);
validForm();
} else {
invalidForm(t('sign_in.check_the_form'));
}
};
handleSignIn = e => {
e.preventDefault();
this.props.fetchSignIn(this.state.formData);
};
render() {
const { auth } = this.props;
const {
requireEmailConfirmation,
emailVerificationLoading,
emailVerificationSuccess,
} = auth;
return (
<SignDialog
open={true}
view={auth.view}
emailVerificationEnabled={requireEmailConfirmation}
emailVerificationLoading={emailVerificationLoading}
emailVerificationSuccess={emailVerificationSuccess}
{...this}
{...this.state}
{...this.props}
/>
);
}
}
const mapStateToProps = state => ({
auth: state.auth,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
facebookCallback,
fetchSignUp,
fetchSignIn,
fetchSignInFacebook,
fetchSignUpFacebook,
fetchForgotPassword,
requestConfirmEmail,
changeView,
hideSignInDialog,
resetSignInDialog,
invalidForm,
validForm,
},
dispatch
);
export default connect(mapStateToProps, mapDispatchToProps)(SignInContainer);
@@ -1,108 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
TextField,
Spinner,
Alert,
} from 'plugin-api/beta/client/components/ui';
import styles from './styles.css';
import t from 'coral-framework/services/i18n';
const SignInContent = ({
handleChange,
formData,
changeView,
handleSignIn,
auth,
fetchSignInFacebook,
}) => {
return (
<div className="coral-sign-in">
<div className={`${styles.header} header`}>
<h1>{t('sign_in.sign_in_to_join')}</h1>
</div>
{auth.error && (
<Alert>
{auth.error.translation_key
? t(`error.${auth.error.translation_key}`)
: auth.error.toString()}
</Alert>
)}
<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,
handleChange: PropTypes.func.isRequired,
changeView: PropTypes.func.isRequired,
emailVerificationLoading: PropTypes.bool.isRequired,
emailVerificationSuccess: PropTypes.bool.isRequired,
resendVerification: PropTypes.func.isRequired,
formData: PropTypes.object,
};
export default SignInContent;
@@ -1,143 +0,0 @@
import styles from './styles.css';
import React from 'react';
import {
Button,
TextField,
Spinner,
Success,
Alert,
} from 'plugin-api/beta/client/components/ui';
import t from 'coral-framework/services/i18n';
class SignUpContent extends React.Component {
componentWillReceiveProps(next) {
if (
!this.props.emailVerificationEnabled &&
!this.props.auth.successSignUp &&
next.auth.successSignUp
) {
setTimeout(() => {
this.props.changeView('SIGNIN');
}, 2000);
}
}
render() {
const {
handleChange,
formData,
emailVerificationEnabled,
auth,
errors,
showErrors,
changeView,
handleSignUp,
fetchSignUpFacebook,
} = this.props;
return (
<div>
<div className={styles.header}>
<h1>{t('sign_in.sign_up')}</h1>
</div>
{auth.error && <Alert>{auth.error}</Alert>}
{!auth.successSignUp && (
<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>
)}
{auth.successSignUp && (
<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,33 +0,0 @@
import React from 'react';
import styles from './styles.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import t from 'coral-framework/services/i18n';
import { logout } from 'coral-framework/actions/auth';
const UserBox = ({ user, logout, onShowProfile }) => (
<div>
{user ? (
<div className={`${styles.userBox} talk-stream-auth-userbox`}>
<span className={styles.userBoxLoggedIn}>
{t('sign_in.logged_in_as')}
</span>
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
<a
className={`${styles.logout} talk-stream-userbox-logout`}
onClick={() => logout()}
>
{t('sign_in.logout')}
</a>
</div>
) : null}
</div>
);
const mapStateToProps = ({ auth }) => ({
user: auth.user,
});
const mapDispatchToProps = dispatch => bindActionCreators({ logout }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(UserBox);
@@ -1,167 +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 {
margin: 10px 0 20px;
letter-spacing: 0.1px;
}
.userBoxLoggedIn {
font-weight: bold;
}
.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;
}
+5 -6
View File
@@ -1,13 +1,12 @@
import UserBox from './components/UserBox';
import SignInButton from './components/SignInButton';
import SignInContainer from './components/SignInContainer';
import ChangeUserNameContainer from './components/ChangeUsername';
import UserBox from './stream/containers/UserBox';
import SignInButton from './stream/containers/SignInButton';
import translations from './translations.yml';
import Login from './login/containers/Main';
export default {
translations,
slots: {
stream: [UserBox, SignInButton, ChangeUserNameContainer],
login: [SignInContainer],
stream: [UserBox, SignInButton],
login: [Login],
},
};
@@ -0,0 +1,8 @@
.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;
font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif;
font-size: 14px;
}
@@ -0,0 +1,13 @@
import React from 'react';
import { Dialog } from 'plugin-api/beta/client/components/ui';
import styles from './Main.css';
import SignIn from '../containers/SignIn';
const SignDialog = () => (
<Dialog className={styles.dialog} id="signInDialog" open={true}>
<SignIn />
</Dialog>
);
export default SignDialog;
@@ -0,0 +1,47 @@
.header {
margin-bottom: 20px;
}
.header h1, .separator h1{
text-align: center;
font-size: 1.2em;
}
.socialConnections {
margin-bottom: 20px;
}
.separator h1{
text-align: center;
font-size: 1.2em;
}
.action {
margin-top: 0px;
}
.signInButton {
margin-top: 10px;
background-color: #2a2a2a;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.recaptcha {
margin-top: 16px;
margin-bottom: 16px;
}
@@ -0,0 +1,140 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
TextField,
Spinner,
Alert,
} from 'plugin-api/beta/client/components/ui';
import styles from './SignIn.css';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
import Recaptcha from 'coral-framework/components/Recaptcha';
class SignIn extends React.Component {
recaptcha = null;
handleForgotPasswordLink = e => {
e.preventDefault();
this.props.onForgotPasswordLink();
};
handleRegisterLink = e => {
e.preventDefault();
this.props.onRegisterLink();
};
handleEmailChange = e => this.props.onEmailChange(e.target.value);
handlePasswordChange = e => this.props.onPasswordChange(e.target.value);
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit();
// Reset recaptcha because each response can only
// be used once.
if (this.recaptcha) {
this.recaptcha.reset();
}
};
handleRecaptchaRef = ref => {
this.recaptcha = ref;
};
render() {
const {
email,
password,
errorMessage,
requireRecaptcha,
loading,
} = this.props;
return (
<div className="coral-sign-in">
<div className={cn(styles.header, 'header')}>
<h1>{t('sign_in.sign_in_to_join')}</h1>
</div>
{errorMessage && <Alert>{errorMessage}</Alert>}
<div>
<div className={cn(styles.socialConnections, 'social-connections')}>
Social
</div>
<div className={styles.separator}>
<h1>{t('sign_in.or')}</h1>
</div>
<form onSubmit={this.handleSubmit}>
<TextField
id="email"
type="email"
label={t('sign_in.email')}
value={email}
style={{ fontSize: 16 }}
onChange={this.handleEmailChange}
/>
<TextField
id="password"
type="password"
label={t('sign_in.password')}
value={password}
style={{ fontSize: 16 }}
onChange={this.handlePasswordChange}
/>
{requireRecaptcha && (
<div className={styles.recaptcha}>
<Recaptcha
className={styles.recaptcha}
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
size="compact"
/>
</div>
)}
<div className={styles.action}>
{!loading ? (
<Button
id="coralLogInButton"
type="submit"
cStyle="black"
className={styles.signInButton}
full
>
{t('sign_in.sign_in')}
</Button>
) : (
<Spinner />
)}
</div>
</form>
</div>
<div className={cn(styles.footer, 'footer')}>
<span>
<a onClick={this.handleForgotPasswordLink}>
{t('sign_in.forgot_your_pass')}
</a>
</span>
<span>
{t('sign_in.need_an_account')}
<a onClick={this.handleRegisterLink} id="coralRegister">
{t('sign_in.register')}
</a>
</span>
</div>
</div>
);
}
}
SignIn.propTypes = {
loading: PropTypes.bool.isRequired,
email: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
onEmailChange: PropTypes.func.isRequired,
onPasswordChange: PropTypes.func.isRequired,
onForgotPasswordLink: PropTypes.func.isRequired,
onRegisterLink: PropTypes.func.isRequired,
onRecaptchaVerify: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
errorMessage: PropTypes.string.isRequired,
requireRecaptcha: PropTypes.bool.isRequired,
};
export default SignIn;
@@ -0,0 +1,10 @@
import React from 'react';
import Main from '../components/Main';
class MainContainer extends React.Component {
render() {
return <Main />;
}
}
export default MainContainer;
@@ -0,0 +1,65 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withSignIn } from 'coral-framework/hocs';
import { compose } from 'recompose';
import SignIn from '../components/SignIn';
class SignInContainer extends Component {
state = {
email: '',
password: '',
recaptchaResponse: '',
};
handleSubmit = () => {
this.props.signIn(
this.state.email,
this.state.password,
this.state.recaptchaResponse
);
};
handleEmailChange = email => {
this.setState({ email });
};
handlePasswordChange = password => {
this.setState({ password });
};
handleRecaptchaVerify = recaptchaResponse => {
this.setState({ recaptchaResponse });
};
componentWillReceiveProps(nextProps) {
if (nextProps.success) {
window.close();
}
}
render() {
return (
<SignIn
onSubmit={this.handleSubmit}
onEmailChange={this.handleEmailChange}
onPasswordChange={this.handlePasswordChange}
email={this.state.email}
password={this.state.password}
errorMessage={this.props.errorMessage}
onRecaptchaVerify={this.handleRecaptchaVerify}
requireRecaptcha={this.props.requireRecaptcha}
loading={this.props.loading}
/>
);
}
}
SignInContainer.propTypes = {
signIn: PropTypes.func.isRequired,
errorMessage: PropTypes.string.isRequired,
requireRecaptcha: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
success: PropTypes.bool.isRequired,
};
export default compose(withSignIn)(SignInContainer);
@@ -10,7 +10,7 @@ import {
import { FakeComment } from './FakeComment';
import t from 'coral-framework/services/i18n';
const CreateUsernameDialog = ({
const SetUsernameDialog = ({
open,
handleClose,
formData,
@@ -70,7 +70,7 @@ const CreateUsernameDialog = ({
</Dialog>
);
CreateUsernameDialog.propTypes = {
SetUsernameDialog.propTypes = {
open: PropTypes.bool,
handleClose: PropTypes.func,
formData: PropTypes.object,
@@ -80,4 +80,4 @@ CreateUsernameDialog.propTypes = {
errors: PropTypes.object,
};
export default CreateUsernameDialog;
export default SetUsernameDialog;
@@ -1,8 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'plugin-api/beta/client/components/ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
import t from 'coral-framework/services/i18n';
const SignInButton = ({ currentUser, showSignInDialog }) => (
@@ -15,11 +13,9 @@ const SignInButton = ({ currentUser, showSignInDialog }) => (
</div>
);
const mapStateToProps = ({ auth }) => ({
currentUser: auth.user,
});
SignInButton.propTypes = {
currentUser: PropTypes.object,
showSignInDialog: PropTypes.func,
};
const mapDispatchToProps = dispatch =>
bindActionCreators({ showSignInDialog }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SignInButton);
export default SignInButton;
@@ -0,0 +1,21 @@
.userBox {
margin: 10px 0 20px;
letter-spacing: 0.1px;
}
.userBoxLoggedIn {
font-weight: bold;
}
.userBox a {
color: black;
font-weight: bold;
cursor: pointer;
margin: 0px;
margin-left: 4px;
padding-bottom: 2px;
}
.userBox .logout {
border-bottom: solid 1px black;
}
@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './UserBox.css';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
const UserBox = ({ user, logout, onShowProfile }) => (
<div>
{user ? (
<div className={cn(styles.userBox, 'talk-stream-auth-userbox')}>
<span className={styles.userBoxLoggedIn}>
{t('sign_in.logged_in_as')}
</span>
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
<a
className={cn(styles.logout, 'talk-stream-userbox-logout')}
onClick={() => logout()}
>
{t('sign_in.logout')}
</a>
</div>
) : null}
</div>
);
UserBox.propTypes = {
user: PropTypes.object,
logout: PropTypes.func,
onShowProfile: PropTypes.func,
};
export default UserBox;
@@ -19,7 +19,7 @@ import {
updateUsername,
} from 'coral-embed-stream/src/actions/login';
class ChangeUsernameContainer extends React.Component {
class SetUsernameDialog extends React.Component {
constructor(props) {
super(props);
@@ -152,7 +152,7 @@ class ChangeUsernameContainer extends React.Component {
}
}
ChangeUsernameContainer.propTypes = {
SetUsernameDialog.propTypes = {
auth: PropTypes.object,
hideCreateUsernameDialog: PropTypes.func,
validForm: PropTypes.func,
@@ -180,4 +180,4 @@ const mapDispatchToProps = dispatch =>
export default compose(
withSetUsername,
connect(mapStateToProps, mapDispatchToProps)
)(ChangeUsernameContainer);
)(SetUsernameDialog);
@@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
import SignInButton from '../components//SignInButton';
const mapStateToProps = ({ auth }) => ({
currentUser: auth.user,
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ showSignInDialog }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SignInButton);
@@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { logout } from 'coral-framework/actions/auth';
import UserBox from '../components/UserBox';
const mapStateToProps = ({ auth }) => ({
user: auth.user,
});
const mapDispatchToProps = dispatch => bindActionCreators({ logout }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(UserBox);
+1
View File
@@ -15,6 +15,7 @@
</head>
<body>
<div id="talk-login-container"></div>
<script src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
<script src="<%= STATIC_URL %>static/coral-login/bundle.js"></script>
</body>
</html>
+2 -2
View File
@@ -7697,8 +7697,8 @@ react-portal@^4.1.2:
prop-types "^15.5.8"
react-recaptcha@^2.2.6:
version "2.3.5"
resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7"
version "2.3.6"
resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.6.tgz#afe07b5552f3ea4d37ecd22d9881c2776719ec2b"
react-redux@^4.4.5:
version "4.4.8"