mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 05:19:41 +08:00
Merge branch 'master' into forgot-password-change
This commit is contained in:
@@ -96,8 +96,6 @@ class UserDetail extends React.Component {
|
||||
bulkReject,
|
||||
} = this.props;
|
||||
|
||||
console.log(rejectedComments, totalComments);
|
||||
|
||||
// if totalComments is 0, you're dividing by zero
|
||||
let rejectedPercent = rejectedComments / totalComments * 100;
|
||||
|
||||
|
||||
@@ -291,5 +291,36 @@ export default {
|
||||
},
|
||||
},
|
||||
}),
|
||||
SetCommentStatus: ({ variables: { status } }) => ({
|
||||
updateQueries: {
|
||||
CoralAdmin_UserDetail: prev => {
|
||||
const increment = {
|
||||
rejectedComments: {
|
||||
$apply: count => (count < prev.totalComments ? count + 1 : count),
|
||||
},
|
||||
};
|
||||
|
||||
const decrement = {
|
||||
rejectedComments: {
|
||||
$apply: count => (count > 0 ? count - 1 : 0),
|
||||
},
|
||||
};
|
||||
|
||||
// If rejected then increment rejectedComments by one
|
||||
if (status === 'REJECTED') {
|
||||
const updated = update(prev, increment);
|
||||
return updated;
|
||||
}
|
||||
|
||||
// If approved then decrement rejectedComments by one
|
||||
if (status === 'ACCEPTED') {
|
||||
const updated = update(prev, decrement);
|
||||
return updated;
|
||||
}
|
||||
|
||||
return prev;
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ export default {
|
||||
'UpdateAssetStatusResponse',
|
||||
'UpdateSettingsResponse',
|
||||
'ChangePasswordResponse',
|
||||
'UpdateEmailAddressResponse'
|
||||
'UpdateEmailAddressResponse',
|
||||
'AttachLocalAuthResponse'
|
||||
),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { gql } from 'react-apollo';
|
||||
import withMutation from '../hocs/withMutation';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
function convertItemType(item_type) {
|
||||
switch (item_type) {
|
||||
@@ -168,36 +167,6 @@ export const withSetCommentStatus = withMutation(
|
||||
errors: null,
|
||||
},
|
||||
},
|
||||
updateQueries: {
|
||||
CoralAdmin_UserDetail: prev => {
|
||||
const increment = {
|
||||
rejectedComments: {
|
||||
$apply: count =>
|
||||
count < prev.totalComments ? count + 1 : count,
|
||||
},
|
||||
};
|
||||
|
||||
const decrement = {
|
||||
rejectedComments: {
|
||||
$apply: count => (count > 0 ? count - 1 : 0),
|
||||
},
|
||||
};
|
||||
|
||||
// If rejected then increment rejectedComments by one
|
||||
if (status === 'REJECTED') {
|
||||
const updated = update(prev, increment);
|
||||
return updated;
|
||||
}
|
||||
|
||||
// If approved then decrement rejectedComments by one
|
||||
if (status === 'ACCEPTED') {
|
||||
const updated = update(prev, decrement);
|
||||
return updated;
|
||||
}
|
||||
|
||||
return prev;
|
||||
},
|
||||
},
|
||||
update: proxy => {
|
||||
const fragment = gql`
|
||||
fragment Talk_SetCommentStatus_Comment on Comment {
|
||||
|
||||
@@ -27,6 +27,5 @@ export {
|
||||
withSetCommentStatus,
|
||||
withChangePassword,
|
||||
withChangeUsername,
|
||||
withUpdateEmailAddress,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
export { compose } from 'recompose';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.errorMsg {
|
||||
color: #FA4643;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.warningIcon {
|
||||
color: #FA4643;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './ErrorMessage.css';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const ErrorMessage = ({ children }) => (
|
||||
<div className={styles.errorMsg}>
|
||||
<Icon className={styles.warningIcon} name="warning" />
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
ErrorMessage.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default ErrorMessage;
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
.detailItem {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detailItemContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.columnDisplay {
|
||||
flex-direction: column;
|
||||
|
||||
.detailItemMessage {
|
||||
padding: 4px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detailItemContent {
|
||||
border: solid 1px #787D80;
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
width: 230px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
|
||||
> .detailIcon {
|
||||
font-size: 1.2em;
|
||||
padding: 0 5px;
|
||||
color: #787D80;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border: solid 2px #FA4643;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
}
|
||||
|
||||
.detailLabel {
|
||||
color: #4C4C4D;
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.detailValue {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
color: #000;
|
||||
outline: none;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detailItemMessage {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 6px;
|
||||
padding-top: 16px;
|
||||
|
||||
.warningIcon, .checkIcon {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
color: #00CD73;
|
||||
}
|
||||
|
||||
.warningIcon {
|
||||
color: #FA4643;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './InputField.css';
|
||||
import ErrorMessage from './ErrorMessage';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const InputField = ({
|
||||
id = '',
|
||||
label = '',
|
||||
type = 'text',
|
||||
name = '',
|
||||
onChange = () => {},
|
||||
showError = true,
|
||||
hasError = false,
|
||||
errorMsg = '',
|
||||
children,
|
||||
columnDisplay = false,
|
||||
showSuccess = false,
|
||||
validationType = '',
|
||||
icon = '',
|
||||
value = '',
|
||||
defaultValue = '',
|
||||
disabled = false,
|
||||
}) => {
|
||||
const inputValue = {
|
||||
...(value ? { value } : {}),
|
||||
...(defaultValue ? { defaultValue } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.detailItem}>
|
||||
<div
|
||||
className={cn(styles.detailItemContainer, {
|
||||
[styles.columnDisplay]: columnDisplay,
|
||||
})}
|
||||
>
|
||||
{label && (
|
||||
<label className={styles.detailLabel} id={id}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
styles.detailItemContent,
|
||||
{ [styles.error]: hasError && showError },
|
||||
{ [styles.disabled]: disabled }
|
||||
)}
|
||||
>
|
||||
{icon && <Icon name={icon} className={styles.detailIcon} />}
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
name={name}
|
||||
className={styles.detailValue}
|
||||
onChange={onChange}
|
||||
autoComplete="off"
|
||||
data-validation-type={validationType}
|
||||
disabled={disabled}
|
||||
{...inputValue}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.detailItemMessage}>
|
||||
{!hasError &&
|
||||
showSuccess &&
|
||||
value && <Icon className={styles.checkIcon} name="check_circle" />}
|
||||
{hasError && showError && <ErrorMessage>{errorMsg}</ErrorMessage>}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InputField.propTypes = {
|
||||
id: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
showError: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
errorMsg: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
columnDisplay: PropTypes.bool,
|
||||
showSuccess: PropTypes.bool,
|
||||
validationType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default InputField;
|
||||
@@ -0,0 +1,82 @@
|
||||
.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: 320px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
font-family: Helvetica,Helvetica Neue,Verdana,sans-serif;
|
||||
color:#3B4A53;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.3em;
|
||||
margin: 15px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1em;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.itemIcon {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex-grow: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
> i.itemIcon {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #787D80;
|
||||
border-radius: 2px;
|
||||
background-color: transparent;
|
||||
height: 30px;
|
||||
font-size: 0.9em;
|
||||
line-height: normal;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
font-size: 1em;
|
||||
|
||||
&:hover {
|
||||
background-color: #eaeaea;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
background-color: transparent;
|
||||
color: #787D80;
|
||||
}
|
||||
|
||||
&.proceed {
|
||||
background-color: #3498DB;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: #FA4643;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import React from 'react';
|
||||
import isMatch from 'lodash/isEqual';
|
||||
import isEqualWith from 'lodash/isEqual';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dialog } from 'plugin-api/beta/client/components/ui';
|
||||
import validate from 'coral-framework/helpers/validate';
|
||||
import errorMsj from 'coral-framework/helpers/error';
|
||||
import { getErrorMessages } from 'coral-framework/utils';
|
||||
import styles from './AddEmailAddressDialog.css';
|
||||
|
||||
import AddEmailContent from './AddEmailContent';
|
||||
import VerifyEmailAddress from './VerifyEmailAddress';
|
||||
import EmailAddressAdded from './EmailAddressAdded';
|
||||
|
||||
const initialState = {
|
||||
step: 0,
|
||||
showErrors: false,
|
||||
errors: {},
|
||||
formData: {},
|
||||
};
|
||||
|
||||
class AddEmailAddressDialog extends React.Component {
|
||||
state = initialState;
|
||||
validKeys = ['emailAddress', 'confirmPassword', 'confirmEmailAddress'];
|
||||
|
||||
onChange = e => {
|
||||
const { name, value, type } = e.target;
|
||||
this.setState(
|
||||
state => ({
|
||||
formData: {
|
||||
...state.formData,
|
||||
[name]: value,
|
||||
},
|
||||
}),
|
||||
() => {
|
||||
this.fieldValidation(value, type, name);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
fieldValidation = (value, type, name) => {
|
||||
if (!value.length) {
|
||||
this.addError({
|
||||
[name]: 'Field is required',
|
||||
});
|
||||
} else if (!validate[type](value)) {
|
||||
this.addError({ [name]: errorMsj[type] });
|
||||
} else {
|
||||
this.removeError(name);
|
||||
}
|
||||
};
|
||||
|
||||
addError = err => {
|
||||
this.setState(({ errors }) => ({
|
||||
errors: { ...errors, ...err },
|
||||
}));
|
||||
};
|
||||
|
||||
removeError = errKey => {
|
||||
this.setState(state => {
|
||||
const { [errKey]: _, ...errors } = state.errors;
|
||||
return {
|
||||
errors,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
hasError = err => {
|
||||
return Object.keys(this.state.errors).indexOf(err) !== -1;
|
||||
};
|
||||
|
||||
formHasError = () => {
|
||||
const formHasErrors = !!Object.keys(this.state.errors).length;
|
||||
const formIncomplete = !isEqualWith(
|
||||
Object.keys(this.state.formData),
|
||||
this.validKeys,
|
||||
isMatch
|
||||
);
|
||||
|
||||
return formHasErrors || formIncomplete;
|
||||
};
|
||||
|
||||
showErrors = () => {
|
||||
this.setState({
|
||||
showErrors: true,
|
||||
});
|
||||
};
|
||||
|
||||
confirmChanges = async () => {
|
||||
if (this.formHasError()) {
|
||||
this.showErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
const { emailAddress, confirmPassword } = this.state.formData;
|
||||
const { attachLocalAuth } = this.props;
|
||||
|
||||
try {
|
||||
await attachLocalAuth({
|
||||
email: emailAddress,
|
||||
password: confirmPassword,
|
||||
});
|
||||
this.props.notify('success', 'Email Added!');
|
||||
this.goToNextStep();
|
||||
} catch (err) {
|
||||
this.props.notify('error', getErrorMessages(err));
|
||||
}
|
||||
};
|
||||
|
||||
goToNextStep = () => {
|
||||
this.setState(({ step }) => ({
|
||||
step: step + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errors, formData, showErrors, step } = this.state;
|
||||
const { root: { settings } } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog className={styles.dialog} open={true}>
|
||||
{step === 0 && (
|
||||
<AddEmailContent
|
||||
formData={formData}
|
||||
errors={errors}
|
||||
showErrors={showErrors}
|
||||
confirmChanges={this.confirmChanges}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
)}
|
||||
{step === 1 &&
|
||||
!settings.requireEmailConfirmation && (
|
||||
<EmailAddressAdded done={() => {}} />
|
||||
)}
|
||||
{step === 1 &&
|
||||
settings.requireEmailConfirmation && (
|
||||
<VerifyEmailAddress
|
||||
emailAddress={formData.emailAddress}
|
||||
done={() => {}}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddEmailAddressDialog.propTypes = {
|
||||
attachLocalAuth: PropTypes.func.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default AddEmailAddressDialog;
|
||||
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './AddEmailAddressDialog.css';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
import InputField from './InputField';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
const AddEmailContent = ({
|
||||
formData,
|
||||
errors,
|
||||
showErrors,
|
||||
confirmChanges,
|
||||
onChange,
|
||||
}) => (
|
||||
<div>
|
||||
<h4 className={styles.title}>
|
||||
{t('talk-plugin-local-auth.add_email.content.title')}
|
||||
</h4>
|
||||
<p className={styles.description}>
|
||||
{t('talk-plugin-local-auth.add_email.content.description')}
|
||||
</p>
|
||||
<ul className={styles.list}>
|
||||
<li className={styles.item}>
|
||||
<Icon name="done" className={styles.itemIcon} />
|
||||
<span className={styles.text}>
|
||||
{t('talk-plugin-local-auth.add_email.content.item_1')}
|
||||
</span>
|
||||
</li>
|
||||
<li className={styles.item}>
|
||||
<Icon name="done" className={styles.itemIcon} />
|
||||
<span className={styles.text}>
|
||||
{t('talk-plugin-local-auth.add_email.content.item_2')}
|
||||
</span>
|
||||
</li>
|
||||
<li className={styles.item}>
|
||||
<Icon name="done" className={styles.itemIcon} />
|
||||
<span className={styles.text}>
|
||||
{t('talk-plugin-local-auth.add_email.content.item_3')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form autoComplete="off">
|
||||
<InputField
|
||||
id="emailAddress"
|
||||
label={t('talk-plugin-local-auth.add_email.enter_email_address')}
|
||||
name="emailAddress"
|
||||
type="email"
|
||||
onChange={onChange}
|
||||
defaultValue=""
|
||||
hasError={!formData.emailAddress || errors.emailAddress}
|
||||
errorMsg={t('talk-plugin-local-auth.add_email.invalid_email_address')}
|
||||
showError={showErrors}
|
||||
columnDisplay
|
||||
showSuccess={false}
|
||||
/>
|
||||
<InputField
|
||||
id="confirmEmailAddress"
|
||||
label={t('talk-plugin-local-auth.add_email.confirm_email_address')}
|
||||
name="confirmEmailAddress"
|
||||
type="email"
|
||||
onChange={onChange}
|
||||
defaultValue=""
|
||||
hasError={
|
||||
!formData.emailAddress ||
|
||||
formData.emailAddress !== formData.confirmEmailAddress
|
||||
}
|
||||
errorMsg={t('talk-plugin-local-auth.add_email.email_does_not_match')}
|
||||
showError={showErrors}
|
||||
columnDisplay
|
||||
showSuccess={false}
|
||||
/>
|
||||
<InputField
|
||||
id="confirmPassword"
|
||||
label={t('talk-plugin-local-auth.add_email.insert_password')}
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={onChange}
|
||||
defaultValue=""
|
||||
hasError={!formData.confirmPassword}
|
||||
showError={showErrors}
|
||||
columnDisplay
|
||||
showSuccess={false}
|
||||
/>
|
||||
<div className={styles.actions}>
|
||||
<a
|
||||
className={cn(styles.button, styles.proceed)}
|
||||
onClick={confirmChanges}
|
||||
>
|
||||
{t('talk-plugin-local-auth.add_email.add_email_address')}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
AddEmailContent.propTypes = {
|
||||
formData: PropTypes.object.isRequired,
|
||||
errors: PropTypes.object.isRequired,
|
||||
showErrors: PropTypes.bool.isRequired,
|
||||
confirmChanges: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AddEmailContent;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './AddEmailAddressDialog.css';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
const EmailAddressAdded = ({ done }) => (
|
||||
<div>
|
||||
<h4 className={styles.title}>
|
||||
{t('talk-plugin-local-auth.add_email.added.title')}
|
||||
</h4>
|
||||
<p className={styles.description}>
|
||||
{t('talk-plugin-local-auth.add_email.added.description')}
|
||||
</p>
|
||||
<strong>{t('talk-plugin-local-auth.add_email.added.subtitle')}</strong>
|
||||
<p className={styles.description}>
|
||||
{t('talk-plugin-local-auth.add_email.added.description_2')}{' '}
|
||||
<strong>{t('talk-plugin-local-auth.add_email.added.path')}</strong>.
|
||||
</p>
|
||||
<div className={styles.actions}>
|
||||
<a className={cn(styles.button, styles.proceed)} onClick={done}>
|
||||
{t('talk-plugin-local-auth.add_email.done')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
EmailAddressAdded.propTypes = {
|
||||
done: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EmailAddressAdded;
|
||||
@@ -62,6 +62,7 @@
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.detailItemMessage {
|
||||
@@ -81,4 +82,4 @@
|
||||
|
||||
.warningIcon {
|
||||
color: #FA4643;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const InputField = ({
|
||||
<div
|
||||
className={cn(
|
||||
styles.detailInput,
|
||||
{ [styles.error]: hasError },
|
||||
{ [styles.error]: hasError && showError },
|
||||
{ [styles.disabled]: disabled }
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './AddEmailAddressDialog.css';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
const VerifyEmailAddress = ({ emailAddress, done }) => (
|
||||
<div>
|
||||
<h4 className={styles.title}>
|
||||
{t('talk-plugin-local-auth.add_email.verify.title')}
|
||||
</h4>
|
||||
<p className={styles.description}>
|
||||
{t('talk-plugin-local-auth.add_email.verify.description', emailAddress)}
|
||||
</p>
|
||||
<div className={styles.actions}>
|
||||
<a className={cn(styles.button, styles.proceed)} onClick={done}>
|
||||
{t('talk-plugin-local-auth.add_email.done')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
VerifyEmailAddress.propTypes = {
|
||||
emailAddress: PropTypes.string.isRequired,
|
||||
done: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default VerifyEmailAddress;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect, withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import AddEmailAddressDialog from '../components/AddEmailAddressDialog';
|
||||
import { notify } from 'coral-framework/actions/notification';
|
||||
|
||||
import { withAttachLocalAuth } from '../hocs';
|
||||
|
||||
const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
|
||||
|
||||
const withData = withFragments({
|
||||
root: gql`
|
||||
fragment TalkPluginLocalAuth_AddEmailAddressDialog_root on RootQuery {
|
||||
me {
|
||||
id
|
||||
email
|
||||
}
|
||||
settings {
|
||||
requireEmailConfirmation
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
export default compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withAttachLocalAuth,
|
||||
withData,
|
||||
excludeIf(({ root: { me } }) => !me || me.email)
|
||||
)(AddEmailAddressDialog);
|
||||
@@ -1,6 +1,50 @@
|
||||
import { withMutation } from 'plugin-api/beta/client/hocs';
|
||||
import { gql } from 'react-apollo';
|
||||
import update from 'immutability-helper';
|
||||
import withMutation from 'coral-framework/hocs/withMutation';
|
||||
|
||||
export const withAttachLocalAuth = withMutation(
|
||||
gql`
|
||||
mutation AttachLocalAuth($input: AttachLocalAuthInput!) {
|
||||
attachLocalAuth(input: $input) {
|
||||
...AttachLocalAuthResponse
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({ mutate }) => ({
|
||||
attachLocalAuth: input => {
|
||||
return mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
update: proxy => {
|
||||
const AttachLocalAuthQuery = gql`
|
||||
query Talk_AttachLocalAuth {
|
||||
me {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const prev = proxy.readQuery({ query: AttachLocalAuthQuery });
|
||||
|
||||
const data = update(prev, {
|
||||
me: {
|
||||
email: { $set: input.email },
|
||||
},
|
||||
});
|
||||
|
||||
proxy.writeQuery({
|
||||
query: AttachLocalAuthQuery,
|
||||
data,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export const withUpdateEmailAddress = withMutation(
|
||||
gql`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ChangePassword from './containers/ChangePassword';
|
||||
import AddEmailAddressDialog from './containers/AddEmailAddressDialog';
|
||||
import Profile from './containers/Profile';
|
||||
import translations from './translations.yml';
|
||||
import graphql from './graphql';
|
||||
@@ -8,6 +9,7 @@ export default {
|
||||
slots: {
|
||||
profileHeader: [Profile],
|
||||
profileSettings: [ChangePassword],
|
||||
stream: [AddEmailAddressDialog],
|
||||
},
|
||||
...graphql,
|
||||
};
|
||||
|
||||
@@ -36,6 +36,29 @@ en:
|
||||
change_email_msg: "Email Address Changed - Your email address has been successfully changed. This email address will now be used for signing in and email notifications."
|
||||
changed_username_success_msg: "Username Changed - Your username has been successfully changed. You will not be able to change your user name for 14 days."
|
||||
change_username_attempt: "Username can't be updated. Usernames can only be changed every 14 days."
|
||||
add_email:
|
||||
add_email_address: "Add Email Address"
|
||||
enter_email_address: "Enter Email Address:"
|
||||
invalid_email_address: "Invalid Email address"
|
||||
confirm_email_address: "Confirm Email Address:"
|
||||
email_does_not_match: "Email Address does not match"
|
||||
insert_password: "Insert Password:"
|
||||
done: "done"
|
||||
content:
|
||||
title: "Add an Email Address"
|
||||
description: "For your added security, we require users to add an email address to their accounts. Your email address will be used to:"
|
||||
item_1: "Receive updates regarding any changes to your account (email address, username, password, etc.)"
|
||||
item_2: "Allow you to download your comments."
|
||||
item_3: "Send comment notifications that you have chosen to receive."
|
||||
verify:
|
||||
title: "Verify Your Email Address"
|
||||
description: "We’ve sent an email to {0} to verify your account. You must verify your email address so that it can be used for account change confirmations and notifications."
|
||||
added:
|
||||
title: "Email Address Added"
|
||||
description: "Your email address has been added to your account."
|
||||
subtitle: "Need to change your email address?"
|
||||
description_2: "You can change your account settings by visiting"
|
||||
path: "My Profile > Settings"
|
||||
es:
|
||||
talk-plugin-local-auth:
|
||||
change_password:
|
||||
|
||||
@@ -5,7 +5,6 @@ const {
|
||||
ErrIncorrectPassword,
|
||||
} = require('./errors');
|
||||
const { get } = require('lodash');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
// hasLocalProfile checks a user's profiles to see if they already have a local
|
||||
// profile associated with their account.
|
||||
@@ -89,7 +88,7 @@ async function attachUserLocalAuth(ctx, email, password) {
|
||||
await Users.isValidPassword(password);
|
||||
|
||||
// Hash the new password.
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const hashedPassword = await Users.hashPassword(password);
|
||||
|
||||
try {
|
||||
// Associate the account with the user.
|
||||
|
||||
+7
-3
@@ -560,7 +560,7 @@ class Users {
|
||||
throw new ErrPasswordTooShort();
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
||||
const hashedPassword = await Users.hashPassword(password);
|
||||
|
||||
return User.update(
|
||||
{ id },
|
||||
@@ -637,7 +637,7 @@ class Users {
|
||||
Users.isValidPassword(password),
|
||||
]);
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
||||
const hashedPassword = await Users.hashPassword(password);
|
||||
|
||||
let user = new User({
|
||||
username,
|
||||
@@ -814,6 +814,10 @@ class Users {
|
||||
return { user, redirect, version };
|
||||
}
|
||||
|
||||
static async hashPassword(password) {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
// TODO: update doc
|
||||
static async resetPassword(token, password) {
|
||||
const { user, redirect, version } = await this.verifyPasswordResetToken(
|
||||
@@ -824,7 +828,7 @@ class Users {
|
||||
throw new ErrPasswordTooShort();
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
||||
const hashedPassword = await Users.hashPassword(password);
|
||||
|
||||
// Update the user's password.
|
||||
await User.update(
|
||||
|
||||
Reference in New Issue
Block a user