diff --git a/assets/css/src/in-app-announcements.styl b/assets/css/src/in-app-announcements.styl index 170d7e31e0..210fdb7f31 100644 --- a/assets/css/src/in-app-announcements.styl +++ b/assets/css/src/in-app-announcements.styl @@ -26,3 +26,16 @@ 100% -moz-box-shadow: 0 0 0 0 rgba(255, 83, 1, 0) box-shadow: 0 0 0 0 rgba(255, 83, 1, 0) + +.mailpoet_in_app_announcement_free_welcome_emails + text-align: center + h2 + font-size: 28px + img + width: 640px + margin-top: -20px + +.mailpoet_in_app_announcement_free_welcome_emails_dot + position: relative + top: -3px + left: 8px diff --git a/assets/img/in_app_announcements/hello-illustration-for-welcome-emails.png b/assets/img/in_app_announcements/hello-illustration-for-welcome-emails.png new file mode 100644 index 0000000000..49f6928759 Binary files /dev/null and b/assets/img/in_app_announcements/hello-illustration-for-welcome-emails.png differ diff --git a/assets/js/src/in_app_announcements/in_app_announcement.jsx b/assets/js/src/in_app_announcements/in_app_announcement.jsx index 56238e3d39..96832b0a42 100644 --- a/assets/js/src/in_app_announcements/in_app_announcement.jsx +++ b/assets/js/src/in_app_announcements/in_app_announcement.jsx @@ -1,25 +1,80 @@ import React from 'react'; +import MailPoet from 'mailpoet'; import InAppAnnouncementDot from './in_app_announcement_dot.jsx'; -const InAppAnnouncement = (props) => { - if (props.newUser !== null && - window.mailpoet_is_new_user !== props.newUser - ) { - return null; +class InAppAnnouncement extends React.Component { + constructor(props) { + super(props); + this.saveDisplayed = this.saveDisplayed.bind(this); + + this.state = { + announcementsSettings: window.mailpoet_in_app_announcements || null, + }; } - if (props.validUntil < (new Date().getTime() / 1000)) { - return null; + saveDisplayed() { + const settings = Object.assign({}, this.state.announcementsSettings); + settings.displayed.push(this.props.showOnlyOnceSlug); + return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, + endpoint: 'settings', + action: 'set', + data: { in_app_announcements: settings }, + }).always(() => (this.setState({ announcementsSettings: settings }))); } - return ( - - {props.children} - ); + render() { + if (this.props.showToNewUser !== null && + window.mailpoet_is_new_user !== this.props.showToNewUser + ) { + return null; + } + + if (this.props.validUntil !== null + && this.props.validUntil < new Date() + ) { + return null; + } + + if (this.props.showToPremiumUser !== null && + window.mailpoet_premium_active !== this.props.showToPremiumUser + ) { + return null; + } + + if (this.props.showOnlyOnceSlug && + this.state.announcementsSettings.displayed.includes(this.props.showOnlyOnceSlug) + ) { + return null; + } + + return ( + { + if (!this.props.showOnlyOnceSlug) { return; } + this.saveDisplayed(); + }} + > + {this.props.children} + + ); + } +} + +const validateBooleanWithWindowDependency = (props, propName, componentName, windowProperty) => { + const propValue = props[propName]; + if (propValue !== null && propValue !== true && propValue !== false) { + return new Error(`Invalid property in ${componentName}. newUser must be of type boolean`); + } + if (propValue !== null && typeof window[windowProperty] === 'undefined') { + return new Error( + `Missing data for evaluation of ${componentName} display condition. ${propName} requires window.${windowProperty}` + ); + } + return null; }; InAppAnnouncement.propTypes = { @@ -27,14 +82,27 @@ InAppAnnouncement.propTypes = { height: React.PropTypes.string, className: React.PropTypes.string, children: React.PropTypes.element.isRequired, - validUntil: React.PropTypes.number, - newUser: (props, propName, componentName) => { + validUntil: React.PropTypes.instanceOf(Date), + showToNewUser: (props, propName, componentName) => ( + validateBooleanWithWindowDependency(props, propName, componentName, 'mailpoet_is_new_user') + ), + showToPremiumUser: (props, propName, componentName) => ( + validateBooleanWithWindowDependency(props, propName, componentName, 'mailpoet_premium_active') + ), + showOnlyOnceSlug: (props, propName, componentName) => { const propValue = props[propName]; - if (propValue !== null && propValue !== true && propValue !== false) { - return new Error(`Invalid property in ${componentName}. newUser must be of type boolean`); + if (propValue !== null && typeof propValue !== 'string') { + return new Error(`Invalid property in ${componentName}. ${propName} must be of type string`); } - if (typeof window.mailpoet_is_new_user === 'undefined') { - return new Error(`Missing data for evaluation of ${componentName} display condition. ${propName} requires window.mailpoet_is_new_user`); + if (propValue === null) { + return null; + } + if ( + typeof window.mailpoet_in_app_announcements === 'undefined' + ) { + return new Error( + `Missing data for evaluation of ${componentName} display condition. ${propName} requires window.mailpoet_in_app_announcements` + ); } return null; }, @@ -45,7 +113,9 @@ InAppAnnouncement.defaultProps = { height: '600px', className: null, validUntil: null, - newUser: null, + showToNewUser: null, + showToPremiumUser: null, + showOnlyOnceSlug: null, }; module.exports = InAppAnnouncement; diff --git a/assets/js/src/in_app_announcements/in_app_announcement_dot.jsx b/assets/js/src/in_app_announcements/in_app_announcement_dot.jsx index 83edc8404b..8241efc448 100644 --- a/assets/js/src/in_app_announcements/in_app_announcement_dot.jsx +++ b/assets/js/src/in_app_announcements/in_app_announcement_dot.jsx @@ -14,21 +14,24 @@ const InAppAnnouncementDot = props => ( width: props.width, height: props.height, }); + if (props.onUserTrigger) props.onUserTrigger(); }} /> ); InAppAnnouncementDot.propTypes = { + children: React.PropTypes.element.isRequired, width: React.PropTypes.string, height: React.PropTypes.string, className: React.PropTypes.string, - children: React.PropTypes.element.isRequired, + onUserTrigger: React.PropTypes.func, }; InAppAnnouncementDot.defaultProps = { width: 'auto', height: 'auto', className: null, + onUserTrigger: null, }; module.exports = InAppAnnouncementDot; diff --git a/assets/js/src/newsletters/listings/heading.jsx b/assets/js/src/newsletters/listings/heading.jsx index 54df9f61e8..9ed6c09cdd 100644 --- a/assets/js/src/newsletters/listings/heading.jsx +++ b/assets/js/src/newsletters/listings/heading.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { Link } from 'react-router'; import MailPoet from 'mailpoet'; +import InAppAnnoucement from 'in_app_announcements/in_app_announcement.jsx'; const ListingHeading = () => (

@@ -17,6 +18,23 @@ const ListingHeading = () => ( > {MailPoet.I18n.t('new')} + +
+

{MailPoet.I18n.t('freeWelcomeEmailsHeading')}

+ {MailPoet.I18n.t('freeWelcomeEmailsHeading')} +

{MailPoet.I18n.t('freeWelcomeEmailsParagraph')}

+
+

); diff --git a/lib/Models/Setting.php b/lib/Models/Setting.php index efcc2ccfae..d962270762 100644 --- a/lib/Models/Setting.php +++ b/lib/Models/Setting.php @@ -53,7 +53,10 @@ class Setting extends Model { ), 'analytics' => array( 'enabled' => false, - ) + ), + 'in_app_announcements' => [ + 'displayed' => [] + ], ); } diff --git a/views/newsletters.html b/views/newsletters.html index 88af5cff15..0c5688d4d9 100644 --- a/views/newsletters.html +++ b/views/newsletters.html @@ -17,6 +17,8 @@ var mailpoet_tracking_enabled = <%= json_encode(tracking_enabled) %>; var mailpoet_premium_active = <%= json_encode(premium_plugin_active) %>; var mailpoet_automatic_emails = <%= json_encode(automatic_emails) %>; + var mailpoet_in_app_announcements = mailpoet_settings.in_app_announcements; + var mailpoet_free_welcome_emails_image = '<%= image_url('in_app_announcements/hello-illustration-for-welcome-emails.png') %>'; <% set newUser = (is_new_user == true) ? 'true' : 'false' %> var mailpoet_is_new_user = <%= newUser %>; @@ -297,6 +299,9 @@ 'introBack': _x('Back', 'A label on a button'), 'introSkip': _x('Skip', 'A label on a button'), 'introDone': _x('Done', 'A label on a button'), + + 'freeWelcomeEmailsHeading': __('Welcome Emails are now free for everyone'), + 'freeWelcomeEmailsParagraph': __('Say “Hello!” automatically to all your new subscribers. They’ll appreciate the extra touch.'), }) %> <% endblock %>