From 047beb1ae992a22b70159c9bbecdcb10c922c8ff Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 5 Mar 2018 17:12:05 +0100 Subject: [PATCH] Implement Require Email for Notifications on Frontend --- client/coral-ui/components/Checkbox.css | 5 ++ client/coral-ui/components/Spinner.js | 10 ++- .../containers/ResendEmailConfirmation.js | 2 +- .../client/containers/Toggle.js | 7 +- .../client/containers/Toggle.js | 7 +- .../client/containers/Toggle.js | 7 +- .../client/components/Banner.css | 42 +++++++++++ .../client/components/Banner.js | 50 +++++++++++++ .../components/EmailVerificationBanner.css | 11 +++ .../components/EmailVerificationBanner.js | 75 +++++++++++++++++++ .../client/components/Settings.css | 5 ++ .../client/components/Settings.js | 14 +++- .../client/components/Toggle.css | 15 ++++ .../client/components/Toggle.js | 18 ++++- .../containers/EmailVerificationBanner.js | 35 +++++++++ .../client/containers/Settings.js | 9 +++ .../client/translations.yml | 10 +++ 17 files changed, 312 insertions(+), 10 deletions(-) create mode 100644 plugins/talk-plugin-notifications/client/components/Banner.css create mode 100644 plugins/talk-plugin-notifications/client/components/Banner.js create mode 100644 plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.css create mode 100644 plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.js create mode 100644 plugins/talk-plugin-notifications/client/containers/EmailVerificationBanner.js diff --git a/client/coral-ui/components/Checkbox.css b/client/coral-ui/components/Checkbox.css index a25ea150f..36e6a73ba 100644 --- a/client/coral-ui/components/Checkbox.css +++ b/client/coral-ui/components/Checkbox.css @@ -50,6 +50,11 @@ color: #00a291; } +.input:disabled + .checkbox:before { + color: #e5e5e5; + cursor: default; +} + .input:focus + .checkbox:before { color: #00a291; } diff --git a/client/coral-ui/components/Spinner.js b/client/coral-ui/components/Spinner.js index 69d805354..3a6e7d30b 100644 --- a/client/coral-ui/components/Spinner.js +++ b/client/coral-ui/components/Spinner.js @@ -1,8 +1,10 @@ import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Spinner.css'; +import cn from 'classnames'; -const Spinner = () => ( -
+const Spinner = ({ className }) => ( +
(
); +Spinner.propTypes = { + className: PropTypes.string, +}; + export default Spinner; diff --git a/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js index bd7c9ad8c..6edeb82f2 100644 --- a/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js +++ b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js @@ -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, }; diff --git a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js index ec7586b16..f888789f3 100644 --- a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js +++ b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js @@ -36,7 +36,11 @@ class ToggleContainer extends React.Component { render() { return ( - + {t('talk-plugin-notifications-category-featured.toggle_description')} ); @@ -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( diff --git a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js index 61600a29f..d261946d0 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js +++ b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js @@ -36,7 +36,11 @@ class ToggleContainer extends React.Component { render() { return ( - + {t('talk-plugin-notifications-category-reply.toggle_description')} ); @@ -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( diff --git a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js index 44d6d5b3c..62a4cbbce 100644 --- a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js +++ b/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js @@ -36,7 +36,11 @@ class ToggleContainer extends React.Component { render() { return ( - + {t('talk-plugin-notifications-category-staff.toggle_description')} ); @@ -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( diff --git a/plugins/talk-plugin-notifications/client/components/Banner.css b/plugins/talk-plugin-notifications/client/components/Banner.css new file mode 100644 index 000000000..cfbff17cf --- /dev/null +++ b/plugins/talk-plugin-notifications/client/components/Banner.css @@ -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; +} + + diff --git a/plugins/talk-plugin-notifications/client/components/Banner.js b/plugins/talk-plugin-notifications/client/components/Banner.js new file mode 100644 index 000000000..401867afb --- /dev/null +++ b/plugins/talk-plugin-notifications/client/components/Banner.js @@ -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 }) => ( +
+
+ +
+
+

{title}

+ {children} +
+
+); + +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; diff --git a/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.css b/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.css new file mode 100644 index 000000000..388abe884 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.css @@ -0,0 +1,11 @@ +.spinner { + display: inline-block; +} + +.link { + display: inline-block; + cursor: pointer; + margin: 2px; + color: #2099d6; + border-bottom: 1px solid #2099d6; +} diff --git a/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.js b/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.js new file mode 100644 index 000000000..b2e344fc7 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/components/EmailVerificationBanner.js @@ -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 }) => ( + +

+ {t('talk-plugin-notifications.banner_info.text')} + { + onResendEmailVerification(); + return false; + }} + > + {t('talk-plugin-notifications.banner_info.verify_now')} + +

+
+); + +const EmailVerificationBannerLoading = () => ( + + + +); + +const EmailVerificationBannerError = ({ errorMessage }) => ( + +

{t('talk-plugin-notifications.banner_error.text')}

+

{errorMessage}

+
+); + +const EmailVerificationBannerSuccess = ({ email }) => ( + +

{t('talk-plugin-notifications.banner_success.text', email)}

+
+); + +const EmailVerificationBanner = ({ + onResendEmailVerification, + email, + success, + loading, + errorMessage, +}) => ( +
+ {success && } + {errorMessage && ( + + )} + {loading && } + {!success && + !errorMessage && + !loading && ( + + )} +
+); + +EmailVerificationBanner.propTypes = { + onResendEmailVerification: PropTypes.func.isRequired, + success: PropTypes.bool.isRequired, + errorMessage: PropTypes.string, + loading: PropTypes.bool.isRequired, + email: PropTypes.string.isRequired, +}; + +export default EmailVerificationBanner; diff --git a/plugins/talk-plugin-notifications/client/components/Settings.css b/plugins/talk-plugin-notifications/client/components/Settings.css index 12a7233e1..c4d43fd9a 100644 --- a/plugins/talk-plugin-notifications/client/components/Settings.css +++ b/plugins/talk-plugin-notifications/client/components/Settings.css @@ -1,5 +1,6 @@ .root { margin-bottom: 20px; + width: 380px; } .innerSettings { @@ -25,3 +26,7 @@ .notifcationSettingsSlot { margin-bottom: 3px; } + +.disabled { + color: #e5e5e5; +} diff --git a/plugins/talk-plugin-notifications/client/components/Settings.js b/plugins/talk-plugin-notifications/client/components/Settings.js index dad6b2bed..010422491 100644 --- a/plugins/talk-plugin-notifications/client/components/Settings.js +++ b/plugins/talk-plugin-notifications/client/components/Settings.js @@ -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 (

{t('talk-plugin-notifications.settings_title')}

-

+ {needEmailVerification && } +

{t('talk-plugin-notifications.settings_subtitle')}

@@ -40,6 +49,7 @@ class Settings extends React.Component { childFactory={this.childFactory} setTurnOffInputFragment={setTurnOffInputFragment} updateNotificationSettings={updateNotificationSettings} + disabled={needEmailVerification} /> -
); } } Toggle.propTypes = { + disabled: PropTypes.bool, checked: PropTypes.bool, onChange: PropTypes.func, children: PropTypes.node, diff --git a/plugins/talk-plugin-notifications/client/containers/EmailVerificationBanner.js b/plugins/talk-plugin-notifications/client/containers/EmailVerificationBanner.js new file mode 100644 index 000000000..0d8a98214 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/containers/EmailVerificationBanner.js @@ -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 ( + + ); + } +} + +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 +); diff --git a/plugins/talk-plugin-notifications/client/containers/Settings.js b/plugins/talk-plugin-notifications/client/containers/Settings.js index 9552ebe5f..61a21f440 100644 --- a/plugins/talk-plugin-notifications/client/containers/Settings.js +++ b/plugins/talk-plugin-notifications/client/containers/Settings.js @@ -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 ( ); } @@ -60,6 +68,7 @@ const enhance = compose( __typename ${getSlotFragmentSpreads(slots, 'root')} me { + email profiles { provider ... on LocalUserProfile { diff --git a/plugins/talk-plugin-notifications/client/translations.yml b/plugins/talk-plugin-notifications/client/translations.yml index 9088e6493..83a34526b 100644 --- a/plugins/talk-plugin-notifications/client/translations.yml +++ b/plugins/talk-plugin-notifications/client/translations.yml @@ -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.