Add welcome wizard application

[MAILPOET-1439]
This commit is contained in:
Rostislav Wolny
2018-08-08 15:51:50 +02:00
committed by pavel-mailpoet
parent d8167692ee
commit e2b40fee37
11 changed files with 558 additions and 0 deletions

View File

@@ -32,3 +32,4 @@
@require '../../../node_modules/react-confirm-alert/src/react-confirm-alert.css' @require '../../../node_modules/react-confirm-alert/src/react-confirm-alert.css'
@require 'newsletter_templates' @require 'newsletter_templates'
@require 'welcome_wizard'

View File

@@ -0,0 +1,118 @@
.mailpoet_welcome_wizard_centered_column
display: flex
flex-direction: column
justify-content: center
align-items: center
.mailpoet_welcome_wizard_header img
margin-bottom: 20px
.mailpoet_welcome_wizard_steps
padding-top: 20px
width: 100%
.mailpoet_welcome_wizard_step_content
margin-top: 40px
max-width: 620px
min-height: 35%vh;
h1
font-weight: 400
padding-bottom: 10px
text-align: center
p
font-weight: normal
font-size: 15px
line-height: 22px
color: #595c65
text-align: center
margin: 10px 0;
#mailpoet_sender_form
margin-top: 30px
width: 330px
label
display: inline-block
font-size: 15px
margin-bottom: 20px
input[type="text"]
margin-top: 10px
font-size: 15px
width: 328px
height: 30px
input[type="submit"]
margin: 50px 0 25px 0
a
font-size: 12px
color: #595c65
.mailpoet_sender_form_loading
opacity: .5
.mailpoet_welcome_wizard_help_info_block
padding-left: 180px
position: relative
margin-bottom: 10px
p
text-align: left
.mailpoet_welcome_wizard_support_button
background: #32a8d9 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAA3lBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ymXGKAAAASXRSTlMAAQIFBwoPEBIUFRscHR8jKCkqLC0/Q0RGSEpNUFFXYHFzeXp9gIKFjKCjpamwvL7Aw8bIy8zN0NLT1dja3ODk5ujq7PT19/3+6Nyy9QAAAklJREFUeAGl1/1W4jwQBvAHBapi0erLK7XsKmLxAyp+SBEVAUXluf8b2ibHnqUk0LT7+3+anJlpMoGe1Wh3B9MZOZsOuu2GBWN2q//NhO9+y4YJtzenxrznIs1xyJXCY6xTDfhj3Gl6jm1ZtuM1O2P+CKpY6eST0sh3kOD4I0qfJ9ArXVG6cwtQFNw7SlclaGzdU3ipY4X6C4X7LSh2n2VN/CJWKvqygs+7yroydlLDWrWJjF5auyT3PKwgRWUod17CIpmrhzJSlR9k1hI1kuuWYaAs116oWFXUd1KBkcpE1LuKWCDyXIOhmsh5gB/HjPgw5jMS93koeqMIY0XRLSEkl5E6Mqgz4kLoiX5G7PKLy74usUT0eQ8Rex5/RviPOodIcknObQAtkqNCtuDCiGQLQF+mOtu2ZcL7gCWq5uCvTWvZJhSO6AwLDZJjZDYm2UCbZAeZdUi20SXZRGZNkl0MSHrIzCM5wFTmKzOH5BQzkjbWlkpXMZvkDIxYgNIkKb1iMfJPwXm3vc2ImjC1w3SNdkDyPW+pXJJPeZvklGSQtz2vSV7k/THeSDaUX9LMPiM7usNALY7ijGSYdgwdQqvwSvJcPQCNgo8Y2VOPXqNt35C81R36m2pPaRf28l03eCQ53Mh30ZXkwrmu2Ph3VC9382BlrMgXrA405sHqKGUerA5x5sHq+GgebDy4/v/IBZlG5qMbJpgP6/tnr0z6MHkmbB+4p9dvXPbxO+sDZehZsYxPI956G/keZQzP95BGfQ7y/Sm4+LUDrT+vKBtiP2phEAAAAABJRU5ErkJggg==') center center no-repeat
background-size: 30px
display: inline-block
height: 54px
width: 54px
border-radius: 28px
position: absolute
top: 10px
left: 70px
box-shadow: 2px 2px 8px #666;
transform: scale(0.8)
.mailpoet_welcome_wizard_video_badge
cursor: default
position: absolute
top: 20px
left: 30px
line-height: 1.5em
padding-bottom: 0
.dashicons
line-height: 1em
&:hover
background: $video-guide-badge-color
.mailpoet_welcome_wizard_mail_icon
background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAABrCAYAAADw4IlaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABT9JREFUeNrsnTFSGz0YhhWSgpJJkS4z9tBS4JbKOUHwDfAJYp8AcwLDCSBdOpwT4IoWCoo0jJ1JlybcIPmEvw0LwyTG1iftrp5nRjGh+PktHmv16t3YzgEA5Mqrp9/Y297pysMnGftMDwRmIuPk8vZm+qyAIt9AHsbMExjTFwnPHgko8vkV75y5gUj0RMLJRukbrHwQk/HfFVD3fRfMCUSmU6yALeYCErCFgJCUDaYAEBAQEAABAQEBYvImwc+8k3F0eXtzzPRXC61iD2VsNVlA/+TG8mQ/yuNQRLzmV59cvF15OJWxm9MluCvjSp78CAWSibclw1diVynkq8oe8FAmYaZ1IMSTr6viDXLbAz5HS8aFTMqZXpbvUMRu1dPLbSXu91x1BZz6IKGBIiQHMmZ6axjYhIyZgXzeg6F6EecSLKuU37t1VvmhS4SUc5ksvyK20CaIeC0/n25xC1TohOvvcu6seqqx1h5Qfuhcxgf5smewGhJSwsg30lUv9B577hY3lfoxTxpC/J2t8tCWcWawGvqQcqVHBfCCkOHDnVuc64XmWFe9SWVCiAaHvjzpz7rJDXn53NXV0D/xI0LKf0PGoVG6vdaQGGzbFfwYxv/PyWhrSAnNQEUkpDwv375ebkPLV7RXnZDymQgYIaS0NKSc66sd8R5CxrlByJjq5dZkL256EO1rNg0pQ4OQcv9q16OF3I9WrgxCxv2Wyv/+1gkZSQUsiXisIWUS+D9d9MoXuYUU/3x9ODM6WvFhsl38211LolVxPjj4yO4WRzahX1Fdl8mRjXF/638vfsXrxwp60btgje4djfKhaXSvbNzfHulebxrzOSXpgvXVNSwd2YR8JfuQ0qhe2bi/nbqEt8UlvRtGQ0rH0Sv/S74DZ9jfashIdk9mJW7Jp1f+59HKqatYf9s4AVXColfuu8x7ZcP+1s/r2v1tIwUsiXh/BOAy7JW1v/Uhw6q/bYfob2sfQpYMKdn0ynXrbxu9Aj4Rcap7Q8teuZtYPqv+1jmj/jYbAYvV0LhXvkjRK2vI8N2tVX/btupvsxKwJGJjeuVSf2txtGLe32YpYEnEY10NLXvllpF4lv3txEXqb7MWUCWcG/fKs5BHNtrfjpxtf9urY+tT6/eGqUOvXOpvLY5WkvS3IXnjak6pV/6qlzaLXvnFRzbG/e217vVq/7YmjXl3LP2nAFa98sC9oFeO0N92mvKeOo17e7aUvXIu/S0CLhdSrHvlwRP5Ri6T/hYBlxfRH0lY9cpj7ZUPcutvCSEvDymWvfKpUcgY1jndsgI+E1KcXa8cksr3twi4xmpoGFLWZepq0t8i4PoiWvbKq4SMWvW3CBhORKteeVlq2d8SQsJK6Fednh4yj12cz82b66o3zX3++ZyQBxEte+VHIcPVvL9lBTQMKc6uV25Mf8sKaC9iyF65cf0tAsYTceTWO7JpZH/LJTh+SPmgd7gseydzcbQyYQZZAUOJeOaW65Ub39+yAqYNKUWv/Mkt7nzZ0hXPX6ZPSLcIGCWkuOpVeVyCARAQEBAAAQEBARAQEBAAAQEBARAQEBAAAQEBARAQEBAQEAABAQEBEBAQEAABAQEBEBAQEAABAQEBEBAQEAABAQEBEBAQEAABoX6s+g6p3b3tnd9MH7ACAgICICAgIAACQi0FnDMVkFJAPsURUnD3qvhqb3tn5uJ8WjjA/VX38vamXd4DDpkTiEjf//G6+NuPXz+/vX/77rt8uc/cgLV8xaeJvi5/VyS8Fgn9x9Vv6uV4k7mCUPs9GV9k9PhEUagMfwQYAFppkb3J5HUdAAAAAElFTkSuQmCC') center center no-repeat
background-size: 40px 27px
display: inline-block
width: 40px
height: 27px
position: absolute
top: 50px
left: 75px
@media screen and (max-width 520px)
.mailpoet_welcome_wizard_help_info_block
padding-left: 150px
.mailpoet_welcome_wizard_support_button
left: 40px
.mailpoet_welcome_wizard_video_badge
left: 10px
.mailpoet_welcome_wizard_mail_icon
left: 50px
.mailpoet_welcome_wizard_step_controls
margin-top: 50px
.button
margin:0 10px
.mailpoet_welcome_wizard_woo_screenshot
margin-top: 30px
width: 400px
@media screen and (max-width 520px)
.mailpoet_welcome_wizard_woo_screenshot
width: 340px
.welcome_wizard_video
position: absolute
top: -1000px

View File

@@ -0,0 +1,21 @@
import React from 'react';
import SteppedProgressBar from '../common/stepped_progess_bar.jsx';
const WelcomeWizardHeader = props => (
<div className="mailpoet_welcome_wizard_centered_column mailpoet_welcome_wizard_header">
<img src={props.logo_src} width="200" alt="MailPoet logo" />
{
props.current_step <= props.steps_count ?
(<SteppedProgressBar steps_count={props.steps_count} step={props.current_step} />)
: null
}
</div>
);
WelcomeWizardHeader.propTypes = {
current_step: React.PropTypes.number.isRequired,
steps_count: React.PropTypes.number.isRequired,
logo_src: React.PropTypes.string.isRequired,
};
module.exports = WelcomeWizardHeader;

View File

@@ -0,0 +1,69 @@
import React from 'react';
import MailPoet from 'mailpoet';
import ReactStringReplace from 'react-string-replace';
const WelcomeWizardHelpInfoStep = props => (
<div className="mailpoet_welcome_wizard_step_content mailpoet_welcome_wizard_centered_column">
<div className="mailpoet_welcome_wizard_help_info_block">
<span className="mailpoet_welcome_wizard_support_button" />
<p>
{
ReactStringReplace(
MailPoet.I18n.t('welcomeWizardYouCanContactText'),
/\[strong\](.*?)\[\/strong\]/g,
match => (<strong key={match}>{ match }</strong>)
)
}
</p>
</div>
<div className="mailpoet_welcome_wizard_help_info_block">
<span className="mailpoet_badge mailpoet_badge_video mailpoet_welcome_wizard_video_badge">
<span className="dashicons dashicons-format-video" />
{MailPoet.I18n.t('seeVideoGuide')}
</span>
<p>
{
ReactStringReplace(
MailPoet.I18n.t('welcomeWizardAboutVideosText'),
/\[strong\](.*?)\[\/strong\]/g,
match => (<strong key={match}>{ match }</strong>)
)
}
</p>
</div>
<div className="mailpoet_welcome_wizard_help_info_block">
<span className="mailpoet_welcome_wizard_mail_icon" />
<p>
{
ReactStringReplace(
MailPoet.I18n.t('welcomeWizardAboutCourseText'),
/\[strong\](.*?)\[\/strong\]/g,
match => (<strong key={match}>{ match }</strong>)
)
}
</p>
<iframe
id="mailpoet_form_iframe"
width="100%"
height="100%"
scrolling="no"
frameBorder="0"
title="Apply to course"
src="https://newsletters.mailpoet.com?mailpoet_form_iframe=13"
className="mailpoet_form_iframe"
marginWidth="0"
marginHeight="0"
allowTransparency="true"
/>
</div>
<button className="button button-primary" onClick={props.next}>{MailPoet.I18n.t('next')}</button>
</div>
);
module.exports = WelcomeWizardHelpInfoStep;
WelcomeWizardHelpInfoStep.propTypes = {
next: React.PropTypes.func.isRequired,
};
module.exports = WelcomeWizardHelpInfoStep;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import MailPoet from 'mailpoet';
const WelcomeWizardMigratedUserStep = props => (
<div className="mailpoet_welcome_wizard_step_content mailpoet_welcome_wizard_centered_column">
<h1>{MailPoet.I18n.t('welcomeWizardLetsStartTitle')}</h1>
<p>{MailPoet.I18n.t('welcomeWizardSenderMigratedUserText')}</p>
<div className="mailpoet_welcome_wizard_step_controls">
<button className="button button-primary" onClick={props.next}>{MailPoet.I18n.t('next')}</button>
</div>
</div>
);
WelcomeWizardMigratedUserStep.propTypes = {
next: React.PropTypes.func.isRequired,
};
module.exports = WelcomeWizardMigratedUserStep;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import MailPoet from 'mailpoet';
import jQuery from 'jquery';
const WelcomeWizardSenderStep = props => (
<div className="mailpoet_welcome_wizard_step_content mailpoet_welcome_wizard_centered_column">
<h1>{MailPoet.I18n.t('welcomeWizardLetsStartTitle')}</h1>
<p>{MailPoet.I18n.t('welcomeWizardSenderText')}</p>
<form
id="mailpoet_sender_form"
className={
`mailpoet_welcome_wizard_centered_column ${(props.loading ? 'mailpoet_sender_form_loading' : '')}`
}
onSubmit={(e) => {
e.preventDefault();
if (!jQuery('#mailpoet_sender_form').parsley().validate()) { return; }
props.submit_sender();
}}
>
<label htmlFor="senderName">
{MailPoet.I18n.t('senderName')}:
<input
name="senderName"
type="text"
placeholder="John Doe"
value={props.sender ? props.sender.name : ''}
data-parsley-required
onChange={e => props.update_sender({ name: e.target.value })}
/>
</label>
<label htmlFor="senderAddress">
{MailPoet.I18n.t('senderAddress')}:
<input
name="senderAddress"
type="text"
placeholder="john@doe.com"
value={props.sender ? props.sender.address : ''}
data-parsley-required
data-parsley-type="email"
onChange={e => props.update_sender({ address: e.target.value })}
/>
</label>
<input className="button button-primary" type="submit" value={MailPoet.I18n.t('next')} />
<a onClick={props.finish} href="#finish">{MailPoet.I18n.t('noThanksSkip')}</a>
</form>
</div>
);
WelcomeWizardSenderStep.propTypes = {
finish: React.PropTypes.func.isRequired,
loading: React.PropTypes.bool.isRequired,
update_sender: React.PropTypes.func.isRequired,
submit_sender: React.PropTypes.func.isRequired,
sender: React.PropTypes.shape({
name: React.PropTypes.string,
address: React.PropTypes.string,
}),
};
WelcomeWizardSenderStep.defaultProps = {
sender: null,
};
module.exports = WelcomeWizardSenderStep;

View File

@@ -0,0 +1,55 @@
import React from 'react';
import MailPoet from 'mailpoet';
import ReactStringReplace from 'react-string-replace';
const WelcomeWizardUsageTrackingStep = props => (
<div className="mailpoet_welcome_wizard_step_content mailpoet_welcome_wizard_centered_column">
<h1>{MailPoet.I18n.t('welcomeWizardUsageTrackingStepTitle')}</h1>
<p>
{
ReactStringReplace(
MailPoet.I18n.t('welcomeWizardTrackingText'),
/\[link\](.*?)\[\/link\]/g,
match => (
<a
key="docs_link"
href="https://beta.docs.mailpoet.com/article/130-sharing-your-data-with-us"
target="_blank"
rel="noopener noreferrer"
>{ match }</a>
)
)
}
</p>
<div
className={
`mailpoet_welcome_wizard_step_controls
${(props.loading ? 'mailpoet_welcome_wizard_step_controls_loading' : '')}`
}
>
<button
className="button"
onClick={props.skip_action}
disabled={props.loading}
>
{MailPoet.I18n.t('skip')}
</button>
<button
className="button button-primary"
onClick={props.allow_action}
disabled={props.loading}
>
{props.allow_text}
</button>
</div>
</div>
);
module.exports = WelcomeWizardUsageTrackingStep;
WelcomeWizardUsageTrackingStep.propTypes = {
allow_action: React.PropTypes.func.isRequired,
allow_text: React.PropTypes.string.isRequired,
skip_action: React.PropTypes.func.isRequired,
loading: React.PropTypes.bool.isRequired,
};

View File

@@ -0,0 +1,41 @@
import React from 'react';
import MailPoet from 'mailpoet';
const WelcomeWizardWooCommerceStep = props => (
<div className="mailpoet_welcome_wizard_step_content mailpoet_welcome_wizard_centered_column">
<h1>{MailPoet.I18n.t('welcomeWizardWooCommerceStepTitle')}</h1>
<p>
{MailPoet.I18n.t('welcomeWizardHelpingShopOwnersText')}
</p>
<p>
{MailPoet.I18n.t('welcomeWizardWooCommerceEmailsText')}
</p>
<img
src={props.screenshot_src}
className="mailpoet_welcome_wizard_woo_screenshot"
alt="WooCommerce email"
/>
<div
className={
`mailpoet_welcome_wizard_step_controls
${(props.loading ? 'mailpoet_welcome_wizard_step_controls_loading' : '')}`
}
>
<button
className="button button-primary"
onClick={props.next}
disabled={props.loading}
>
{MailPoet.I18n.t('gotIt')}
</button>
</div>
</div>
);
module.exports = WelcomeWizardWooCommerceStep;
WelcomeWizardWooCommerceStep.propTypes = {
next: React.PropTypes.func.isRequired,
screenshot_src: React.PropTypes.string.isRequired,
loading: React.PropTypes.bool.isRequired,
};

View File

@@ -0,0 +1,147 @@
import React from 'react';
import MailPoet from 'mailpoet';
import WelcomeWizardHeader from './header.jsx';
import WelcomeWizardSenderStep from './steps/sender_step.jsx';
import WelcomeWizardMigratedUserStep from './steps/migrated_user_step.jsx';
import WelcomeWizardHelpInfoStep from './steps/help_info_step.jsx';
import WelcomeWizardUsageTrackingStep from './steps/usage_tracking_step.jsx';
import WelcomeWizardWooCommerceStep from './steps/woo_commerce_step.jsx';
class WelcomeWizardStepsController extends React.Component {
constructor(props) {
super(props);
this.state = {
stepsCount: window.is_woocommerce_active ? 4 : 3,
shouldSetSender: !window.is_mp2_migration_complete,
loading: false,
sender: window.sender_data,
};
this.finishWizard = this.finishWizard.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.activateTracking = this.activateTracking.bind(this);
this.updateSender = this.updateSender.bind(this);
this.submitSender = this.submitSender.bind(this);
this.showWooCommerceStepOrFinish = this.showWooCommerceStepOrFinish.bind(this);
this.componentDidUpdate();
}
componentDidUpdate() {
const step = parseInt(this.props.params.step, 10);
if (step > this.state.stepsCount || step < 1) {
this.props.router.push('steps/1');
}
}
finishWizard() {
this.setState({ loading: true });
window.location = window.finish_wizard_url;
}
showWooCommerceStepOrFinish() {
if (this.state.stepsCount === 4) {
this.props.router.push('steps/4');
} else {
this.finishWizard();
}
}
updateSettings(data) {
this.setState({ loading: true });
return MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'settings',
action: 'set',
data,
}).then(() => this.setState({ loading: false })
).fail((response) => {
this.setState({ loading: false });
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(error => error.message),
{ scroll: true }
);
}
});
}
activateTracking() {
this.updateSettings({ analytics: { enabled: true } }).then(() => (
this.showWooCommerceStepOrFinish())
);
}
updateSender(data) {
this.setState({
sender: Object.assign({}, this.state.sender, data),
});
}
submitSender() {
this.updateSettings({ sender: this.state.sender }).then(() => (this.props.router.push('steps/2')));
}
render() {
const step = parseInt(this.props.params.step, 10);
return (
<div className="mailpoet_welcome_wizard_steps mailpoet_welcome_wizard_centered_column">
<WelcomeWizardHeader
current_step={step}
steps_count={this.state.stepsCount}
logo_src={window.mailpoet_logo_url}
/>
{ step === 1 && this.state.shouldSetSender ?
<WelcomeWizardSenderStep
update_sender={this.updateSender}
submit_sender={this.submitSender}
finish={this.finishWizard}
loading={this.state.loading}
sender={this.state.sender}
/> : null
}
{ step === 1 && !this.state.shouldSetSender ?
<WelcomeWizardMigratedUserStep
next={() => this.props.router.push('steps/2')}
/> : null
}
{ step === 2 ?
<WelcomeWizardHelpInfoStep
next={() => this.props.router.push('steps/3')}
/> : null
}
{ step === 3 ?
<WelcomeWizardUsageTrackingStep
skip_action={this.showWooCommerceStepOrFinish}
allow_action={this.activateTracking}
allow_text={this.state.stepsCount === 4 ?
MailPoet.I18n.t('allowAndContinue') : MailPoet.I18n.t('allowAndFinish')}
loading={this.state.loading}
/> : null
}
{ step === 4 ?
<WelcomeWizardWooCommerceStep
next={this.finishWizard}
screenshot_src={window.woocommerce_screenshot_url}
loading={this.state.loading}
/> : null
}
</div>
);
}
}
WelcomeWizardStepsController.propTypes = {
params: React.PropTypes.shape({
step: React.PropTypes.string.isRequired,
}).isRequired,
router: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}).isRequired,
};
module.exports = WelcomeWizardStepsController;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';
import WelcomeWizardStepsController from './steps_controller.jsx';
const container = document.getElementById('welcome_wizard_container');
if (container) {
const history = useRouterHistory(createHashHistory)({ queryKey: false });
ReactDOM.render((
<Router history={history}>
<Route path={'/'}>
<IndexRedirect to={'steps/1'} />
<Route
path={'steps/:step'}
component={WelcomeWizardStepsController}
/>
</Route>
</Router>
), container);
}

View File

@@ -277,6 +277,7 @@ var adminConfig = {
'settings/reinstall_from_scratch.js', 'settings/reinstall_from_scratch.js',
'subscribers/importExport/import.js', 'subscribers/importExport/import.js',
'subscribers/importExport/export.js', 'subscribers/importExport/export.js',
'welcome_wizard/wizard.jsx'
], ],
form_editor: [ form_editor: [
'form_editor/form_editor.js', 'form_editor/form_editor.js',