mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 05:34:11 +08:00
Implement Require Email for Notifications on Frontend
This commit is contained in:
@@ -50,6 +50,11 @@
|
||||
color: #00a291;
|
||||
}
|
||||
|
||||
.input:disabled + .checkbox:before {
|
||||
color: #e5e5e5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.input:focus + .checkbox:before {
|
||||
color: #00a291;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user