Implement Require Email for Notifications on Frontend

This commit is contained in:
Chi Vinh Le
2018-03-05 17:12:05 +01:00
parent 8113c4f8b8
commit 047beb1ae9
17 changed files with 312 additions and 10 deletions
+5
View File
@@ -50,6 +50,11 @@
color: #00a291;
}
.input:disabled + .checkbox:before {
color: #e5e5e5;
cursor: default;
}
.input:focus + .checkbox:before {
color: #00a291;
}
+8 -2
View File
@@ -1,8 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './Spinner.css';
import cn from 'classnames';
const Spinner = () => (
<div className={styles.container}>
const Spinner = ({ className }) => (
<div className={cn(styles.container, className)}>
<svg
className={styles.spinner}
width="40px"
@@ -23,4 +25,8 @@ const Spinner = () => (
</div>
);
Spinner.propTypes = {
className: PropTypes.string,
};
export default Spinner;
@@ -41,7 +41,7 @@ ResendEmailConfirmatonContainer.propTypes = {
success: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
resendEmailConfirmation: PropTypes.func.isRequired,
errorMessage: PropTypes.string.isRequired,
errorMessage: PropTypes.string,
setView: PropTypes.func.isRequired,
email: PropTypes.string.isRequired,
};
@@ -36,7 +36,11 @@ class ToggleContainer extends React.Component {
render() {
return (
<Toggle checked={this.getOnFeaturedSetting()} onChange={this.toggle}>
<Toggle
checked={this.getOnFeaturedSetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-featured.toggle_description')}
</Toggle>
);
@@ -50,6 +54,7 @@ ToggleContainer.propTypes = {
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
@@ -36,7 +36,11 @@ class ToggleContainer extends React.Component {
render() {
return (
<Toggle checked={this.getOnReplySetting()} onChange={this.toggle}>
<Toggle
checked={this.getOnReplySetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-reply.toggle_description')}
</Toggle>
);
@@ -50,6 +54,7 @@ ToggleContainer.propTypes = {
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
@@ -36,7 +36,11 @@ class ToggleContainer extends React.Component {
render() {
return (
<Toggle checked={this.getOnReplySetting()} onChange={this.toggle}>
<Toggle
checked={this.getOnReplySetting()}
onChange={this.toggle}
disabled={this.props.disabled}
>
{t('talk-plugin-notifications-category-staff.toggle_description')}
</Toggle>
);
@@ -50,6 +54,7 @@ ToggleContainer.propTypes = {
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired,
};
const enhance = compose(
@@ -0,0 +1,42 @@
.root {
display: flex;
border: 1px solid #a8afb3;
border-radius: 2px;
margin: 16px 0;
p:first-of-type {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
}
.leftColumn {
width: 32px;
background-color: #787d80;
text-align: center;
padding-top: 6px;
font-size: 18px;
flex-shrink: 0;
&.error {
background-color: #fa6265;
}
&.success {
background-color: #00cd7e;
}
}
.icon {
color: white;
}
.rightColumn {
padding: 8px 12px 10px 12px;
font-size: 14px;
}
.title {
font-size: 14px;
margin: 0;
margin-bottom: 4px;
}
@@ -0,0 +1,50 @@
import React from 'react';
import styles from './Banner.css';
import { Icon } from 'coral-ui';
import PropTypes from 'prop-types';
import cn from 'classnames';
function getIcon(icon, error, success) {
if (icon) {
return icon;
}
if (error) {
return 'warning';
}
if (success) {
return 'done';
}
return 'info';
}
const Banner = ({ title, icon, error, success, children }) => (
<section className={styles.root}>
<div
className={cn(styles.leftColumn, {
[styles.error]: error,
[styles.success]: success,
})}
>
<Icon name={getIcon(icon, error, success)} className={styles.icon} />
</div>
<div className={styles.rightColumn}>
<h1 className={styles.title}>{title}</h1>
{children}
</div>
</section>
);
Banner.propTypes = {
title: PropTypes.string,
icon: PropTypes.string,
children: PropTypes.node,
error: PropTypes.bool,
success: PropTypes.bool,
};
Banner.defaultProps = {
title: 'Title',
children: 'Lorem Ipsum Dolot Sit Ahmet',
};
export default Banner;
@@ -0,0 +1,11 @@
.spinner {
display: inline-block;
}
.link {
display: inline-block;
cursor: pointer;
margin: 2px;
color: #2099d6;
border-bottom: 1px solid #2099d6;
}
@@ -0,0 +1,75 @@
import React from 'react';
import Banner from './Banner';
import PropTypes from 'prop-types';
import { Spinner } from 'plugin-api/beta/client/components/ui';
import { t } from 'plugin-api/beta/client/services';
import styles from './EmailVerificationBanner.css';
const EmailVerificationBannerInfo = ({ onResendEmailVerification }) => (
<Banner icon="email" title={t('talk-plugin-notifications.banner_info.title')}>
<p>
{t('talk-plugin-notifications.banner_info.text')}
<a
className={styles.link}
onClick={() => {
onResendEmailVerification();
return false;
}}
>
{t('talk-plugin-notifications.banner_info.verify_now')}
</a>
</p>
</Banner>
);
const EmailVerificationBannerLoading = () => (
<Banner icon="email" title={t('talk-plugin-notifications.banner_info.title')}>
<Spinner className={styles.spinner} />
</Banner>
);
const EmailVerificationBannerError = ({ errorMessage }) => (
<Banner title={t('talk-plugin-notifications.banner_error.title')} error>
<p>{t('talk-plugin-notifications.banner_error.text')}</p>
<p>{errorMessage}</p>
</Banner>
);
const EmailVerificationBannerSuccess = ({ email }) => (
<Banner title={t('talk-plugin-notifications.banner_success.title')} success>
<p>{t('talk-plugin-notifications.banner_success.text', email)}</p>
</Banner>
);
const EmailVerificationBanner = ({
onResendEmailVerification,
email,
success,
loading,
errorMessage,
}) => (
<div>
{success && <EmailVerificationBannerSuccess email={email} />}
{errorMessage && (
<EmailVerificationBannerError errorMessage={errorMessage} />
)}
{loading && <EmailVerificationBannerLoading />}
{!success &&
!errorMessage &&
!loading && (
<EmailVerificationBannerInfo
onResendEmailVerification={onResendEmailVerification}
/>
)}
</div>
);
EmailVerificationBanner.propTypes = {
onResendEmailVerification: PropTypes.func.isRequired,
success: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
loading: PropTypes.bool.isRequired,
email: PropTypes.string.isRequired,
};
export default EmailVerificationBanner;
@@ -1,5 +1,6 @@
.root {
margin-bottom: 20px;
width: 380px;
}
.innerSettings {
@@ -25,3 +26,7 @@
.notifcationSettingsSlot {
margin-bottom: 3px;
}
.disabled {
color: #e5e5e5;
}
@@ -5,6 +5,8 @@ import { Slot } from 'plugin-api/beta/client/components';
import { t } from 'plugin-api/beta/client/services';
import styles from './Settings.css';
import { BareButton } from 'plugin-api/beta/client/components/ui';
import EmailVerificationBanner from '../containers/EmailVerificationBanner';
import cn from 'classnames';
class Settings extends React.Component {
childFactory = el => {
@@ -23,13 +25,20 @@ class Settings extends React.Component {
updateNotificationSettings,
turnOffAll,
turnOffButtonDisabled,
needEmailVerification,
email,
} = this.props;
return (
<IfSlotIsNotEmpty slot="notificationSettings" queryData={{ root }}>
<div className={styles.root}>
<h3>{t('talk-plugin-notifications.settings_title')}</h3>
<h4 className={styles.subtitle}>
{needEmailVerification && <EmailVerificationBanner email={email} />}
<h4
className={cn(styles.subtitle, {
[styles.disabled]: needEmailVerification,
})}
>
{t('talk-plugin-notifications.settings_subtitle')}
</h4>
<div className={styles.innerSettings}>
@@ -40,6 +49,7 @@ class Settings extends React.Component {
childFactory={this.childFactory}
setTurnOffInputFragment={setTurnOffInputFragment}
updateNotificationSettings={updateNotificationSettings}
disabled={needEmailVerification}
/>
<BareButton
className={styles.turnOffButton}
@@ -63,6 +73,8 @@ Settings.propTypes = {
updateNotificationSettings: PropTypes.func.isRequired,
turnOffAll: PropTypes.func.isRequired,
turnOffButtonDisabled: PropTypes.bool.isRequired,
needEmailVerification: PropTypes.bool.isRequired,
email: PropTypes.string,
};
export default Settings;
@@ -3,4 +3,19 @@
width: 270px;
cursor: pointer;
user-select: none;
&.disabled {
color: #e5e5e5;
cursor: default;
}
}
.toggle {
display: flex;
align-items: center;
}
.checkBox {
width: 100%;
text-align: right;
}
@@ -3,24 +3,36 @@ import PropTypes from 'prop-types';
import { Checkbox } from 'plugin-api/beta/client/components/ui';
import styles from './Toggle.css';
import uuid from 'uuid/v4';
import cn from 'classnames';
class Toggle extends React.Component {
id = uuid();
render() {
const { checked, onChange, children } = this.props;
const { checked, onChange, children, disabled } = this.props;
return (
<div className={styles.toggle}>
<label htmlFor={this.id} className={styles.title}>
<label
htmlFor={this.id}
className={cn(styles.title, { [styles.disabled]: disabled })}
>
{children}
</label>
<Checkbox checked={checked} onChange={onChange} id={this.id} />
<div className={styles.checkBox}>
<Checkbox
checked={checked}
onChange={onChange}
id={this.id}
disabled={disabled}
/>
</div>
</div>
);
}
}
Toggle.propTypes = {
disabled: PropTypes.bool,
checked: PropTypes.bool,
onChange: PropTypes.func,
children: PropTypes.node,
@@ -0,0 +1,35 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withResendEmailConfirmation } from 'plugin-api/beta/client/hocs';
import { compose } from 'recompose';
import EmailVerificationBanner from '../components/EmailVerificationBanner';
class EmailVerificationBannerContainer extends Component {
handleResendEmailVerification = () => {
this.props.resendEmailConfirmation(this.props.email);
};
render() {
return (
<EmailVerificationBanner
onResendEmailVerification={this.handleResendEmailVerification}
errorMessage={this.props.errorMessage}
success={this.props.success}
loading={this.props.loading}
email={this.props.email}
/>
);
}
}
EmailVerificationBannerContainer.propTypes = {
success: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
resendEmailConfirmation: PropTypes.func.isRequired,
errorMessage: PropTypes.string,
email: PropTypes.string.isRequired,
};
export default compose(withResendEmailConfirmation)(
EmailVerificationBannerContainer
);
@@ -31,6 +31,12 @@ class SettingsContainer extends React.Component {
this.props.updateNotificationSettings(this.state.turnOffInput);
};
getNeedEmailVerification() {
return !this.props.root.me.profiles.some(
profile => profile.provider === 'local' && profile.confirmedAt
);
}
render() {
return (
<Settings
@@ -42,6 +48,8 @@ class SettingsContainer extends React.Component {
updateNotificationSettings={this.props.updateNotificationSettings}
turnOffAll={this.turnOffAll}
turnOffButtonDisabled={this.state.hasNotifications.length === 0}
needEmailVerification={this.getNeedEmailVerification()}
email={this.props.root.me.email}
/>
);
}
@@ -60,6 +68,7 @@ const enhance = compose(
__typename
${getSlotFragmentSpreads(slots, 'root')}
me {
email
profiles {
provider
... on LocalUserProfile {
@@ -3,3 +3,13 @@ en:
settings_title: Notifications
settings_subtitle: Receive notifications when
turn_off_all: I do not want to receive notifications
banner_info:
title: Email Verification Required
text: To receive email notifications you must have a verified email address.
verify_now: Verify your email now
banner_success:
title: Email Verification Sent
text: An email has been sent to {0} containing a verification link.
banner_error:
title: Error
text: There have been an error sending your verification email. Please try again later.