Refactor to use WP Redux store and actions

[MAILPOET-2681]
This commit is contained in:
Amine Ben hammou
2020-03-30 06:15:27 +02:00
committed by Veljko V
parent 2516529394
commit 7f79165147
9 changed files with 300 additions and 238 deletions

View File

@@ -1,137 +1,37 @@
import React, { useState } from 'react'; import React, { useContext } from 'react';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import { useSetting, useSelector } from 'settings/store/hooks'; import { useSelector, useAction } from 'settings/store/hooks';
import { GlobalContext } from 'context';
import { t } from 'common/functions';
import { KeyMessages, MssMessages, PremiumMessages } from './messages'; import { KeyMessages, MssMessages, PremiumMessages } from './messages';
const getSettings = async () => MailPoet.Ajax.post({ export default function KeyActivation() {
api_version: window.mailpoet_api_version, const { notices } = useContext<any>(GlobalContext);
endpoint: 'settings', const {
action: 'get', key, isKeyValid, premiumStatus, premiumMessage, mssStatus, mssMessage, premiumInstallationStatus,
}); } = useSelector('getKeyActivationState')();
const setState = useAction('updateKeyActivationState');
const verifyMssKey = useAction('verifyMssKey');
const verifyPremiumKey = useAction('verifyPremiumKey');
const installPremiumPlugin = useAction('installPremiumPlugin');
const activatePremiumPlugin = useAction('activatePremiumPlugin');
const requestServicesApi = async (key, action) => MailPoet.Ajax.post({ const verifyKey = async (event) => {
api_version: window.mailpoet_api_version, if (!key) {
endpoint: 'services', notices.error(<p>{t('premiumTabNoKeyNotice')}</p>, { scroll: true });
action, return;
data: { key },
});
const requestPremiumApi = async (action) => MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'premium',
action,
});
const activateMss = async (key) => MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'settings',
action: 'set',
data: {
mta_group: 'mailpoet',
mta: {
method: 'MailPoet',
mailpoet_api_key: key,
},
signup_confirmation: {
enabled: '1',
},
},
});
const PremiumTab = () => {
const [premiumKey] = useSetting('premium', 'premium_key');
const [mssKey] = useSetting('mta', 'mailpoet_api_key');
const [key, setKey] = useState(premiumKey || mssKey);
const getMssStatus = useSelector('getMssStatus');
const getPremiumStatus = useSelector('getPremiumStatus');
const [premiumStatus, setPremiumStatus] = useState(key ? getPremiumStatus() : null);
const [premiumMessage, setPremiumMessage] = useState(null);
const [premiumInstallationStatus, setPremiumInstallationStatus] = useState(null);
const [mssStatus, setMssStatus] = useState(key ? getMssStatus() : null);
const [mssKeyMessage, setMssKeyMessage] = useState(null);
const keyValid = useSelector('hasValidKey')();
const activatePremiumPlugin = async (isAfterInstall = false) => {
const activateStatus = isAfterInstall ? 'install_activating' : 'activate_activating';
const doneStatus = isAfterInstall ? 'install_done' : 'activate_done';
const errorStatus = isAfterInstall ? 'install_activating_error' : 'activate_error';
setPremiumInstallationStatus(activateStatus);
try {
await requestPremiumApi('activatePlugin');
} catch (error) {
setPremiumInstallationStatus(errorStatus);
return false;
} }
setPremiumInstallationStatus(doneStatus); await setState({
return true; mssStatus: null,
premiumStatus: null,
premiumInstallationStatus: null,
});
MailPoet.Modal.loading(true);
await verifyMssKey(key, event.isTrusted);
await verifyPremiumKey(key);
MailPoet.Modal.loading(false);
}; };
const installPremiumPlugin = async () => {
setPremiumInstallationStatus('install_installing');
try {
await requestPremiumApi('installPlugin');
} catch (error) {
setPremiumInstallationStatus('install_installing_error');
return false;
}
return activatePremiumPlugin(true);
};
const verifyMailPoetPremiumKey = async () => {
try {
const response = await requestServicesApi(key, 'checkPremiumKey');
setPremiumMessage(null);
// install/activate Premium plugin
let pluginActive = response.meta.premium_plugin_active;
if (!response.meta.premium_plugin_installed) {
setPremiumStatus('valid_premium_plugin_being_installed');
pluginActive = await installPremiumPlugin();
} else if (!response.meta.premium_plugin_active) {
setPremiumStatus('valid_premium_plugin_being_activated');
pluginActive = await activatePremiumPlugin();
} else {
setPremiumStatus('valid_premium_plugin_active');
}
MailPoet.trackEvent(
'User has validated a Premium key',
{
'MailPoet Free version': MailPoet.version,
'Premium plugin is active': pluginActive,
}
);
} catch (error) {
setPremiumStatus('invalid');
setPremiumMessage(error.errors.map((e) => e.message).join(' ') || null);
MailPoet.trackEvent(
'User has failed to validate a Premium key',
{
'MailPoet Free version': MailPoet.version,
'Premium plugin is active': !!MailPoet.premiumVersion,
}
);
}
};
async function verifyMailPoetSendingServiceKey(activateMssIfKeyValid) {
try {
const response = await requestServicesApi(key, 'checkMSSKey');
setMssKeyMessage(response.data.message || null);
if (activateMssIfKeyValid) {
await activateMss(key);
setMssStatus('valid_mss_active');
} else {
setMssStatus('valid_mss_not_active');
}
} catch (error) {
setMssStatus('invalid');
setMssKeyMessage(error.errors.map((e) => e.message).join(' ') || null);
}
window.updateMSSActivationUI();
}
return ( return (
<table className="form-table"> <table className="form-table">
<tbody> <tbody>
@@ -152,47 +52,29 @@ const PremiumTab = () => {
className="regular-text" className="regular-text"
name="premium[premium_key]" name="premium[premium_key]"
value={key || ''} value={key || ''}
onChange={(event) => { onChange={(event) => setState({
setKey(event.target.value.trim() || null); mssStatus: null,
setPremiumStatus(null); premiumStatus: null,
setPremiumInstallationStatus(null); premiumInstallationStatus: null,
setMssStatus(null); key: event.target.value.trim() || null,
}} })}
/> />
<button <button
type="button" type="button"
id="mailpoet_premium_key_verify" id="mailpoet_premium_key_verify"
className="button-secondary" className="button-secondary"
onClick={async (event) => { onClick={verifyKey}
if (!key) {
MailPoet.Notice.error(
MailPoet.I18n.t('premiumTabNoKeyNotice'),
{ scroll: true },
);
return;
}
setPremiumStatus(null);
setPremiumInstallationStatus(null);
setMssStatus(null);
MailPoet.Modal.loading(true);
const isUserTriggered = event.isTrusted;
await verifyMailPoetSendingServiceKey(isUserTriggered);
await verifyMailPoetPremiumKey();
MailPoet.Modal.loading(false);
}}
> >
{MailPoet.I18n.t('premiumTabVerifyButton')} {MailPoet.I18n.t('premiumTabVerifyButton')}
</button> </button>
</div> </div>
{keyValid !== null && ( {isKeyValid !== null && (
<div className="key-activation-messages"> <div className="key-activation-messages">
<KeyMessages /> <KeyMessages />
{mssStatus !== null && ( {mssStatus !== null && (
<MssMessages <MssMessages
keyMessage={mssKeyMessage} keyMessage={mssMessage}
activationCallback={() => verifyMailPoetSendingServiceKey(true)} activationCallback={() => verifyMssKey(key, true)}
/> />
)} )}
{premiumStatus !== null && ( {premiumStatus !== null && (
@@ -200,7 +82,7 @@ const PremiumTab = () => {
keyMessage={premiumMessage} keyMessage={premiumMessage}
installationStatus={premiumInstallationStatus} installationStatus={premiumInstallationStatus}
installationCallback={installPremiumPlugin} installationCallback={installPremiumPlugin}
activationCallback={() => activatePremiumPlugin()} activationCallback={() => activatePremiumPlugin(false)}
/> />
)} )}
</div> </div>
@@ -238,4 +120,4 @@ const PremiumTab = () => {
} }
</> </>
); );
}; }

View File

@@ -15,6 +15,6 @@ const KeyNotValidMessage = () => (
); );
export default function KeyMessages() { export default function KeyMessages() {
const hasValidKey = useSelector('hasValidKey')(); const { isKeyValid } = useSelector('getKeyActivationState')();
return hasValidKey ? <KeyValidMessage /> : <KeyNotValidMessage />; return isKeyValid ? <KeyValidMessage /> : <KeyNotValidMessage />;
} }

View File

@@ -32,8 +32,8 @@ type Props = {
activationCallback: () => any, activationCallback: () => any,
} }
export default function MssMessages(props: Props) { export default function MssMessages(props: Props) {
const mssKeyStatus = useSelector('getMssStatus')(); const { mssStatus } = useSelector('getKeyActivationState')();
switch (mssKeyStatus) { switch (mssStatus) {
case 'valid_mss_active': case 'valid_mss_active':
return <ActiveMessage />; return <ActiveMessage />;
case 'valid_mss_not_active': case 'valid_mss_not_active':

View File

@@ -58,19 +58,19 @@ type Props = {
installationStatus: PremiumInstallationStatus installationStatus: PremiumInstallationStatus
} }
export default function PremiumMessages(props: Props) { export default function PremiumMessages(props: Props) {
const keyStatus = useSelector('getPremiumStatus')(); const { premiumStatus: status } = useSelector('getKeyActivationState')();
return ( return (
<> <>
{keyStatus === 'valid_premium_plugin_active' && <ActiveMessage />} {status === 'valid_premium_plugin_active' && <ActiveMessage />}
{keyStatus === 'valid_premium_plugin_not_active' && ( {status === 'valid_premium_plugin_not_active' && (
<PremiumNotActiveMessage callback={props.activationCallback} /> <PremiumNotActiveMessage callback={props.activationCallback} />
)} )}
{keyStatus === 'valid_premium_plugin_not_installed' && ( {status === 'valid_premium_plugin_not_installed' && (
<PremiumNotInstalledMessage callback={props.installationCallback} /> <PremiumNotInstalledMessage callback={props.installationCallback} />
)} )}
{keyStatus === 'valid_premium_plugin_being_installed' && <InstallingMessage />} {status === 'valid_premium_plugin_being_installed' && <InstallingMessage />}
{keyStatus === 'valid_premium_plugin_being_activated' && <ActivatingMessage />} {status === 'valid_premium_plugin_being_activated' && <ActivatingMessage />}
{keyStatus === 'invalid' && <NotValidMessage message={props.keyMessage} />} {status === 'invalid' && <NotValidMessage message={props.keyMessage} />}
<PremiumInstallationMessages installationStatus={props.installationStatus} /> <PremiumInstallationMessages installationStatus={props.installationStatus} />
</> </>
); );

View File

@@ -1,6 +1,7 @@
import { select } from '@wordpress/data'; import { select } from '@wordpress/data';
import MailPoet from 'mailpoet';
import { STORE_NAME } from '.'; import { STORE_NAME } from '.';
import { Action } from './types'; import { Action, KeyActivationState } from './types';
export function setSetting(path: string[], value: any): Action { export function setSetting(path: string[], value: any): Action {
return { type: 'SET_SETTING', path, value }; return { type: 'SET_SETTING', path, value };
@@ -28,6 +29,10 @@ export function* openWoocommerceCustomizer(newsletterId?: string) {
return null; return null;
} }
export function updateKeyActivationState(fields: Partial<KeyActivationState>): Action {
return { type: 'UPDATE_KEY_ACTIVATION_STATE', fields };
}
export function* saveSettings() { export function* saveSettings() {
yield { type: 'SAVE_STARTED' }; yield { type: 'SAVE_STARTED' };
const data = select(STORE_NAME).getSettings(); const data = select(STORE_NAME).getSettings();
@@ -43,3 +48,144 @@ export function* saveSettings() {
yield { type: 'TRACK_SETTINGS_SAVED' }; yield { type: 'TRACK_SETTINGS_SAVED' };
return { type: 'SAVE_DONE' }; return { type: 'SAVE_DONE' };
} }
export function* verifyMssKey(key: string, activateMssIfKeyValid: boolean) {
const { success, error, res } = yield {
type: 'CALL_API',
endpoint: 'services',
action: 'checkMSSKey',
data: { key },
};
if (!success) {
return updateKeyActivationState({
mssStatus: 'invalid',
mssMessage: error.join(' ') || null,
});
}
const fields: Partial<KeyActivationState> = {
mssMessage: res.data.message || null,
};
if (activateMssIfKeyValid) {
const call = yield {
type: 'CALL_API',
endpoint: 'settings',
action: 'set',
data: {
mta_group: 'mailpoet',
mta: { method: 'MailPoet', mailpoet_api_key: key },
signup_confirmation: { enabled: '1' },
},
};
if (!call.success) {
fields.mssStatus = 'valid_mss_not_active';
} else {
yield setSetting(['mta_group'], 'mailpoet');
yield setSetting(['mta', 'method'], 'MailPoet');
yield setSetting(['mta', 'mailpoet_api_key'], key);
yield setSetting(['signup_confirmation', 'enabled'], '1');
fields.mssStatus = 'valid_mss_active';
}
} else {
fields.mssStatus = 'valid_mss_not_active';
}
return updateKeyActivationState(fields);
}
export function* verifyPremiumKey(key: string) {
const { res, success, error } = yield {
type: 'CALL_API',
endpoint: 'services',
action: 'checkPremiumKey',
data: { key },
};
if (!success) {
MailPoet.trackEvent(
'User has failed to validate a Premium key',
{
'MailPoet Free version': MailPoet.version,
'Premium plugin is active': !!MailPoet.premiumVersion,
}
);
return updateKeyActivationState({
premiumStatus: 'invalid',
premiumMessage: error.join(' ') || null,
});
}
yield updateKeyActivationState({ premiumMessage: null });
// install/activate Premium plugin
let pluginInstalled = res.meta.premium_plugin_installed;
let pluginActive = res.meta.premium_plugin_active;
if (!pluginInstalled) {
const actions = installPremiumPlugin();
let action = actions.next();
while (!action.done) {
yield action.value;
action = actions.next();
}
pluginInstalled = action.value;
}
if (pluginInstalled && !pluginActive) {
const isAfterInstall = !res.meta.premium_plugin_installed;
const actions = activatePremiumPlugin(isAfterInstall);
let action = actions.next();
while (!action.done) {
yield action.value;
action = actions.next();
}
pluginActive = action.value;
}
if (pluginInstalled && pluginActive) {
yield updateKeyActivationState({ premiumStatus: 'valid_premium_plugin_active' });
}
MailPoet.trackEvent(
'User has validated a Premium key',
{
'MailPoet Free version': MailPoet.version,
'Premium plugin is active': pluginActive,
}
);
return null;
}
export function* activatePremiumPlugin(isAfterInstall) {
const doneStatus = isAfterInstall ? 'install_done' : 'activate_done';
const errorStatus = isAfterInstall ? 'install_activating_error' : 'activate_error';
yield updateKeyActivationState({
premiumStatus: 'valid_premium_plugin_being_activated',
premiumInstallationStatus: isAfterInstall ? 'install_activating' : 'activate_activating',
});
const call = yield {
type: 'CALL_API',
endpoint: 'premium',
action: 'activatePlugin',
};
if (call && !call.success) {
yield updateKeyActivationState({ premiumInstallationStatus: errorStatus });
return false;
}
yield updateKeyActivationState({ premiumInstallationStatus: doneStatus });
return true;
}
export function* installPremiumPlugin() {
yield updateKeyActivationState({
premiumStatus: 'valid_premium_plugin_being_installed',
premiumInstallationStatus: 'install_installing',
});
const call = yield {
type: 'CALL_API',
endpoint: 'premium',
action: 'installPlugin',
};
if (call && !call.success) {
yield updateKeyActivationState({ premiumInstallationStatus: 'install_installing_error' });
return false;
}
return true;
}

View File

@@ -1,7 +1,8 @@
import _ from 'lodash'; import _ from 'lodash';
import { State, Action } from './types'; import { State, Action, KeyActivationState } from './types';
export default function createReducer(defaultValue: State) { export default function createReducer(defaultValue: State) {
let keyActivation: KeyActivationState;
return (state: State = defaultValue, action: Action): State => { return (state: State = defaultValue, action: Action): State => {
switch (action.type) { switch (action.type) {
case 'SET_SETTING': case 'SET_SETTING':
@@ -14,6 +15,16 @@ export default function createReducer(defaultValue: State) {
return { ...state, save: { inProgress: false, error: null } }; return { ...state, save: { inProgress: false, error: null } };
case 'SAVE_FAILED': case 'SAVE_FAILED':
return { ...state, save: { inProgress: false, error: action.error } }; return { ...state, save: { inProgress: false, error: action.error } };
case 'UPDATE_KEY_ACTIVATION_STATE':
keyActivation = { ...state.keyActivation, ...action.fields };
keyActivation.isKeyValid = null;
if (keyActivation.mssStatus !== null || keyActivation.premiumStatus !== null) {
keyActivation.isKeyValid = (
keyActivation.mssStatus !== 'invalid'
|| keyActivation.premiumStatus !== 'invalid'
);
}
return { ...state, keyActivation };
default: default:
return state; return state;
} }

View File

@@ -1,22 +1,58 @@
import { State } from './types'; import MailPoet from 'mailpoet';
import { State, PremiumStatus, MssStatus } from './types';
import normalizeSettings from './normalize_settings'; import normalizeSettings from './normalize_settings';
export default function makeDefaultState(window: any): State { export default function makeDefaultState(window: any): State {
return { const pages = window.mailpoet_pages;
save: { const segments = window.mailpoet_segments;
inProgress: false, const save = { inProgress: false, error: null };
error: null, const data = normalizeSettings(window.mailpoet_settings);
}, const flags = {
flags: {
error: false, error: false,
woocommerce: !!window.mailpoet_woocommerce_active, woocommerce: !!window.mailpoet_woocommerce_active,
newUser: !!window.mailpoet_is_new_user, newUser: !!window.mailpoet_is_new_user,
mssKeyValid: window.mailpoet_mss_key_valid, mssKeyValid: window.mailpoet_mss_key_valid,
premiumKeyValid: window.mailpoet_premium_key_valid, premiumKeyValid: window.mailpoet_premium_key_valid,
premiumPluginInstalled: window.mailpoet_premium_plugin_installed, premiumPluginInstalled: window.mailpoet_premium_plugin_installed,
}, };
data: normalizeSettings(window.mailpoet_settings), const premiumStatus = getPremiumStatus(flags);
segments: window.mailpoet_segments, const mssStatus = getMssStatus(flags, data);
pages: window.mailpoet_pages, let isKeyValid = null;
if (mssStatus !== null || premiumStatus !== null) {
isKeyValid = mssStatus !== 'invalid' || premiumStatus !== 'invalid';
}
const keyActivation = {
mssStatus,
isKeyValid,
premiumStatus,
mssMessage: null,
premiumMessage: null,
premiumInstallationStatus: null,
key: data.premium.premium_key || data.mta.mailpoet_api_key,
};
return {
data, flags, save, keyActivation, segments, pages,
}; };
} }
function getPremiumStatus(flags): PremiumStatus {
const keyValid = flags.premiumKeyValid;
const pluginInstalled = flags.premiumPluginInstalled;
const pluginActive = !!MailPoet.premiumVersion;
if (!keyValid) {
return 'invalid';
}
if (pluginActive) {
return 'valid_premium_plugin_active';
}
return pluginInstalled
? 'valid_premium_plugin_not_active'
: 'valid_premium_plugin_not_installed';
}
function getMssStatus(flags, data): MssStatus {
const keyValid = flags.mssKeyValid;
if (!keyValid) return 'invalid';
const mssActive = data.mta.method === 'MailPoet';
return mssActive ? 'valid_mss_active' : 'valid_mss_not_active';
}

View File

@@ -48,32 +48,6 @@ export function getPages(state: State) {
return state.pages; return state.pages;
} }
export function getPremiumStatus(state: State): PremiumStatus { export function getKeyActivationState(state: State) {
const keyValid = state.flags.premiumKeyValid; return state.keyActivation;
const pluginInstalled = state.flags.premiumPluginInstalled;
const pluginActive = !!MailPoet.premiumVersion;
if (!keyValid) {
return 'invalid';
}
if (pluginActive) {
return 'valid_premium_plugin_active';
}
return pluginInstalled
? 'valid_premium_plugin_not_active'
: 'valid_premium_plugin_not_installed';
}
export function getMssStatus(state: State): MssStatus {
const keyValid = state.flags.mssKeyValid;
const mssActive = isMssActive(state);
if (!keyValid) {
return 'invalid';
}
return mssActive ? 'valid_mss_active' : 'valid_mss_not_active';
}
export function hasValidKey(state: State) {
const hasValidMssKey = getMssStatus(state) !== 'invalid';
const hasValidPremiumKey = getPremiumStatus(state) !== 'invalid';
return hasValidMssKey || hasValidPremiumKey;
} }

View File

@@ -177,30 +177,6 @@ type Page = {
confirm: string confirm: string
} }
} }
export type State = {
data: Settings
segments: Segment[]
pages: Page[]
flags: {
woocommerce: boolean
newUser: boolean
error: boolean
mssKeyValid: boolean
premiumKeyValid: boolean
premiumPluginInstalled: boolean
}
save: {
inProgress: boolean
error: any
}
}
export type Action =
| { type: 'SET_SETTING'; value: any; path: string[] }
| { type: 'SET_ERROR_FLAG'; value: boolean }
| { type: 'SAVE_STARTED' }
| { type: 'SAVE_DONE' }
| { type: 'SAVE_FAILED'; error: any }
export type PremiumStatus = export type PremiumStatus =
| 'invalid' | 'invalid'
@@ -224,3 +200,40 @@ export type PremiumInstallationStatus =
| 'activate_activating' | 'activate_activating'
| 'activate_done' | 'activate_done'
| 'activate_error' | 'activate_error'
export type KeyActivationState = {
key: string
isKeyValid: boolean
premiumStatus: PremiumStatus
premiumMessage: string
mssStatus: MssStatus
mssMessage: string
premiumInstallationStatus: PremiumInstallationStatus
}
export type State = {
data: Settings
segments: Segment[]
pages: Page[]
flags: {
woocommerce: boolean
newUser: boolean
error: boolean
mssKeyValid: boolean
premiumKeyValid: boolean
premiumPluginInstalled: boolean
}
save: {
inProgress: boolean
error: any
}
keyActivation: KeyActivationState
}
export type Action =
| { type: 'SET_SETTING'; value: any; path: string[] }
| { type: 'SET_ERROR_FLAG'; value: boolean }
| { type: 'SAVE_STARTED' }
| { type: 'SAVE_DONE' }
| { type: 'SAVE_FAILED'; error: any }
| { type: 'UPDATE_KEY_ACTIVATION_STATE', fields: Partial<KeyActivationState> }