Refactor mail templates frontend, update backend refactor

This commit is contained in:
Ivan Diaz 2018-11-14 19:58:32 -03:00
parent e6b23c7f29
commit 614b5a1a67
10 changed files with 281 additions and 121 deletions

View File

@ -18,28 +18,31 @@ import SubmitButton from 'core-components/submit-button';
class AdminPanelEmailTemplates extends React.Component {
state = {
loaded: false,
items: [],
formLoading: false,
selectedIndex: 0,
loadingList: true,
loadingTemplate: false,
templates: [],
loadingForm: false,
selectedIndex: -1,
edited: false,
errors: {},
language: 'en',
form: {
title: '',
content: ''
subject: '',
text1: '',
text2: '',
text3: '',
}
};
componentDidMount() {
this.retrieveEmailTemplates();
this.retrieveMailTemplateList();
}
render() {
return (
<div className="admin-panel-email-templates">
<Header title={i18n('EMAIL_TEMPLATES')} description={i18n('EMAIL_TEMPLATES_DESCRIPTION')} />
{(this.state.loaded) ? this.renderContent() : this.renderLoading()}
{(!this.state.loadingList) ? this.renderContent() : this.renderLoading()}
</div>
);
}
@ -50,33 +53,7 @@ class AdminPanelEmailTemplates extends React.Component {
<div className="col-md-3">
<Listing {...this.getListingProps()}/>
</div>
<div className="col-md-9">
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={this.state.language} onChange={event => this.onItemChange(this.state.selectedIndex, event.target.value)} fieldProps={{
type: 'allowed',
size: 'medium'
}}/>
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('TITLE')} name="title" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} />
<div className="admin-panel-email-templates__actions">
<div className="admin-panel-email-templates__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
<div className="admin-panel-email-templates__optional-buttons">
{(this.state.edited) ? this.renderDiscardButton() : null}
<div className="admin-panel-email-templates__recover-button">
<Button onClick={this.onRecoverClick.bind(this)} size="medium">
{i18n('RECOVER_DEFAULT')}
</Button>
</div>
</div>
</div>
</Form>
</div>
{(this.state.selectedIndex != -1) ? this.renderForm() : null}
</div>
);
}
@ -89,6 +66,42 @@ class AdminPanelEmailTemplates extends React.Component {
);
}
renderForm() {
return (
<div className="col-md-9">
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={this.state.language} onChange={event => this.onItemChange(this.state.selectedIndex, event.target.value)} fieldProps={{
type: 'allowed',
size: 'medium'
}}/>
<Form {...this.getFormProps()}>
<div className="row">
<div className="col-md-7">
<FormField label={i18n('SUBJECT')} name="subject" validation="TITLE" required fieldProps={{size: 'large'}}/>
</div>
</div>
<FormField label={i18n('TEXT') + '1'} name="text1" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} />
{(this.state.form.text2) ? <FormField label={i18n('TEXT') + '2'} name="text2" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} /> : null}
{(this.state.form.text3) ? <FormField label={i18n('TEXT') + '3'} name="text3" validation="TEXT_AREA" required decorator={'textarea'} fieldProps={{className: 'admin-panel-email-templates__text-area'}} /> : null}
<div className="admin-panel-email-templates__actions">
<div className="admin-panel-email-templates__save-button">
<SubmitButton type="secondary" size="small">{i18n('SAVE')}</SubmitButton>
</div>
<div className="admin-panel-email-templates__optional-buttons">
{(this.state.edited) ? this.renderDiscardButton() : null}
<div className="admin-panel-email-templates__recover-button">
<Button onClick={this.onRecoverClick.bind(this)} size="medium">
{i18n('RECOVER_DEFAULT')}
</Button>
</div>
</div>
</div>
</Form>
</div>
);
}
renderDiscardButton() {
return (
<div className="admin-panel-email-templates__discard-button">
@ -102,7 +115,7 @@ class AdminPanelEmailTemplates extends React.Component {
getListingProps() {
return {
title: i18n('EMAIL_TEMPLATES'),
items: this.getItems(),
items: this.getTemplateItems(),
selectedIndex: this.state.selectedIndex,
onChange: this.onItemChange.bind(this)
};
@ -112,49 +125,79 @@ class AdminPanelEmailTemplates extends React.Component {
return {
values: this.state.form,
errors: this.state.errors,
loading: this.state.formLoading,
loading: this.state.loadingForm,
onChange: (form) => {this.setState({form, edited: true})},
onValidateErrors: (errors) => {this.setState({errors})},
onSubmit: this.onFormSubmit.bind(this)
}
}
getItems() {
return this.state.items.map((item) => {
getTemplateItems() {
return this.state.templates.map((template) => {
return {
content: item.type
content: template
};
});
}
onItemChange(index, language) {
if(this.state.edited) {
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index, language));
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.retrieveEmailTemplate.bind(this, index, language || this.state.language));
} else {
this.updateForm(index, language);
this.retrieveEmailTemplate(index, language || this.state.language);
}
}
onFormSubmit(form) {
this.setState({formLoading: true});
const {selectedIndex, language, templates} = this.state;
this.setState({loadingForm: true});
API.call({
path: '/system/edit-mail-template',
data: {
templateType: this.state.items[this.state.selectedIndex].type,
subject: form.title,
body: form.content,
language: this.state.language
template: templates[selectedIndex],
language,
subject: form.subject,
text1: form.text1,
text2: form.text2,
text3: form.text3,
}
}).then(() => {
this.setState({formLoading: false});
this.retrieveEmailTemplates();
this.setState({loadingForm: false, edited: false});
}).catch(response => {
this.setState({
loadingForm: false,
});
switch(response.message) {
case 'INVALID_SUBJECT':
this.setState({
errors: {subject: 'Invalid syntax'}
});
break;
case 'INVALID_TEXT_1':
this.setState({
errors: {text1: 'Invalid syntax'}
});
break;
case 'INVALID_TEXT_2':
this.setState({
errors: {text2: 'Invalid syntax'}
});
break;
case 'INVALID_TEXT_3':
this.setState({
errors: {text3: 'Invalid syntax'}
});
break;
}
});
}
onDiscardChangesClick(event) {
event.preventDefault();
this.onItemChange(this.state.selectedIndex);
this.onItemChange(this.state.selectedIndex, this.state.language);
}
onRecoverClick(event) {
@ -163,73 +206,45 @@ class AdminPanelEmailTemplates extends React.Component {
}
recoverEmailTemplate() {
const {selectedIndex, language, templates} = this.state;
API.call({
path: '/system/recover-mail-template',
data: {
templateType: this.state.items[this.state.selectedIndex].type,
language: this.state.language
template: templates[selectedIndex],
language
}
}).then(() => {
this.retrieveEmailTemplates();
this.retrieveEmailTemplate(this.state.selectedIndex, language);
});
}
updateForm(index, language) {
let form = _.clone(this.state.form);
let items = this.state.items;
language = language || this.state.language;
form.title = (items[index] && items[index][language].subject) || '';
form.content = (items[index] && items[index][language].body) || '';
retrieveEmailTemplate(index, language) {
this.setState({
selectedIndex: index,
language: language,
edited: false,
formLoading: false,
form: form,
errors: {}
loadingForm: true,
});
API.call({
path: '/system/get-mail-template',
data: {template: this.state.templates[index], language}
}).then((result) => this.setState({
language,
selectedIndex: index,
edited: false,
loadingForm: false,
form: result.data,
errors: {},
}));
}
retrieveEmailTemplates() {
return API.call({
path: '/system/get-mail-templates',
retrieveMailTemplateList() {
API.call({
path: '/system/get-mail-template-list',
data: {}
}).then((result) => this.setState({
edited: false,
loaded: true,
items: this.getParsedItems(result.data)
}, this.updateForm.bind(this, this.state.selectedIndex)));
}
getParsedItems(items) {
let parsedItems = {};
_.forEach(items, (item) => {
if(parsedItems[item.type]) {
parsedItems[item.type][item.language] = {
subject: item.subject,
body: item.body
};
} else {
parsedItems[item.type] = {
[item.language]: {
subject: item.subject,
body: item.body
}
};
}
});
parsedItems = Object.keys(parsedItems).map((type) => {
return _.extend({
type: type
}, parsedItems[type]);
});
return parsedItems;
loadingList: false,
templates: result.data
}));
}
}

View File

@ -2,7 +2,7 @@
&__text-area {
width: 100%;
height: 157px;
height: 45px;
}
&__save-button {
@ -23,4 +23,4 @@
display: inline-block;
margin-left: 10px;
}
}
}

View File

@ -45,4 +45,4 @@
padding-bottom: 10px;
}
}
}
}

View File

@ -10,6 +10,7 @@ require_once 'system/add-department.php';
require_once 'system/edit-department.php';
require_once 'system/delete-department.php';
require_once 'system/get-logs.php';
require_once 'system/get-mail-template-list.php';
require_once 'system/get-mail-template.php';
require_once 'system/edit-mail-template.php';
require_once 'system/recover-mail-template.php';
@ -41,6 +42,7 @@ $systemControllerGroup->addController(new AddDepartmentController);
$systemControllerGroup->addController(new EditDepartmentController);
$systemControllerGroup->addController(new DeleteDepartmentController);
$systemControllerGroup->addController(new GetLogsController);
$systemControllerGroup->addController(new GetMailTemplateListController);
$systemControllerGroup->addController(new GetMailTemplateController);
$systemControllerGroup->addController(new EditMailTemplateController);
$systemControllerGroup->addController(new RecoverMailTemplateController);

View File

@ -24,6 +24,9 @@ use Respect\Validation\Validator as DataValidator;
* @apiUse INVALID_TEMPLATE
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_SUBJECT
* @apiUse INVALID_TEXT_1
* @apiUse INVALID_TEXT_2
* @apiUse INVALID_TEXT_3
*
* @apiSuccess {Object} data Empty object
*
@ -33,6 +36,11 @@ class EditMailTemplateController extends Controller {
const PATH = '/edit-mail-template';
const METHOD = 'POST';
private $langauge;
private $templateType;
private $subject;
private $texts;
public function validations() {
return [
'permission' => 'staff_3',
@ -54,25 +62,82 @@ class EditMailTemplateController extends Controller {
}
public function handler() {
$language = Controller::request('language');
$templateType = Controller::request('template');
$subject = Controller::request('subject', true);
$texts = [
$this->language = Controller::request('language');
$this->templateType = Controller::request('template');
$this->subject = Controller::request('subject', true);
$this->texts = [
Controller::request('text1'),
Controller::request('text2'),
Controller::request('text3'),
];
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$language, $templateType]);
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$this->language, $this->templateType]);
if($mailTemplate->isNull()) {
throw new Exception(ERRORS::INVALID_TEMPLATE);
}
$mailTemplate->subject = $subject;
$mailTemplate->text1 = $texts[0];
$mailTemplate->text2 = $texts[1];
$mailTemplate->text3 = $texts[2];
$this->validateReplacements();
$mailTemplate->subject = $this->subject;
$mailTemplate->text1 = $this->texts[0];
$mailTemplate->text2 = $this->texts[1];
$mailTemplate->text3 = $this->texts[2];
$mailTemplate->store();
Response::respondSuccess();
}
public function validateReplacements() {
$originalText = MailTexts::getTexts()[$this->language][$this->templateType];
if(!$this->includes(
$this->getReplacementStrings($originalText[1]),
$this->getReplacementStrings($this->texts[0])
)) {
throw new Exception(ERRORS::INVALID_TEXT_1);
}
if(!$this->includes(
$this->getReplacementStrings($originalText[2]),
$this->getReplacementStrings($this->texts[1])
)) {
throw new Exception(ERRORS::INVALID_TEXT_2);
}
if(!$this->includes(
$this->getReplacementStrings($originalText[3]),
$this->getReplacementStrings($this->texts[2])
)) {
throw new Exception(ERRORS::INVALID_TEXT_3);
}
}
public function includes($array1, $array2) {
foreach($array1 as $item) {
if(!in_array($item, $array2)) return false;
}
return true;
}
public function getReplacementStrings($string) {
$replacements = [];
for($i=0; $i<strlen($string)-1; $i++) {
if($string[$i] == '{' && $string[$i+1] == '{') {
$replacement = "";
$i += 2;
for(; $i<strlen($string)-1;$i++) {
if($string[$i] == '}' && $string[$i+1] == '}') break;
$replacement .= $string[$i];
}
$replacements[] = $replacement;
}
}
return $replacements;
}
}

View File

@ -0,0 +1,36 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-template-list Get mail template
* @apiVersion 4.3.0
*
* @apiName Get mail template list
*
* @apiGroup System
*
* @apiDescription This path retrieves the list of mail templates
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)[]} data Array of mail templates
*
*/
class GetMailTemplateListController extends Controller {
const PATH = '/get-mail-template-list';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
Response::respondSuccess(array_keys(MailTemplate::getFilePaths()));
}
}

View File

@ -18,7 +18,7 @@ use Respect\Validation\Validator as DataValidator;
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)[]} data Array of mail templates
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)} data Data of the mail template
*
*/

View File

@ -187,6 +187,18 @@
* @apiDefine USER_DISABLED
* @apiError {String} USER_DISABLED User is disabled
*/
/**
* @apiDefine INVALID_TEXT_1
* @apiError {String} INVALID_TEXT_1 text1 of mail template has an invalid syntax or missing variables
*/
/**
* @apiDefine INVALID_TEXT_2
* @apiError {String} INVALID_TEXT_2 text2 of mail template has an invalid syntax or missing variables
*/
/**
* @apiDefine INVALID_TEXT_3
* @apiError {String} INVALID_TEXT_3 text3 of mail template has an invalid syntax or missing variables
*/
class ERRORS {
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
@ -237,4 +249,7 @@ class ERRORS {
const ALREADY_DISABLED = 'ALREADY_DISABLED';
const ALREADY_ENABLED = 'ALREADY_ENABLED';
const USER_DISABLED = 'USER_DISABLED';
const INVALID_TEXT_1 = 'INVALID_TEXT_1';
const INVALID_TEXT_2 = 'INVALID_TEXT_2';
const INVALID_TEXT_3 = 'INVALID_TEXT_3';
}

View File

@ -8,7 +8,9 @@ use RedBeanPHP\Facade as RedBean;
* @apiParam {String} type The type of the mail template.
* @apiParam {String} subject The subject of the mail template.
* @apiParam {string} language The language of the mail template.
* @apiParam {String} body The body of the mail template.
* @apiParam {String} text1 First paragraph of the mail template.
* @apiParam {String} text2 Second paragraph of the mail template.
* @apiParam {String} text3 Thrid paragraph of the mail template.
*/
class MailTemplate extends DataStore {

View File

@ -69,8 +69,8 @@ describe 'Mail templates' do
template: 'USER_SIGNUP',
subject: 'new subject',
text1: 'new text1',
text2: 'new text2',
text3: 'new text3',
text2: 'new text2 {{name}}',
text3: 'new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}',
})
(result['status']).should.equal('success')
@ -80,9 +80,34 @@ describe 'Mail templates' do
(row['template']).should.equal('USER_SIGNUP')
(row['subject']).should.equal('new subject')
(row['text1']).should.equal('new text1')
(row['text2']).should.equal('new text2')
(row['text3']).should.equal('new text3')
(row['text2']).should.equal('new text2 {{name}}')
(row['text3']).should.equal('new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}')
end
it 'should fail if one of the texts has invalid syntax' do
result = request('/system/edit-mail-template', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
language: 'en',
template: 'USER_SIGNUP',
subject: 'new subject',
text1: 'new text1',
text2: 'new text2',
text3: 'new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}',
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TEXT_2')
row = $database.getRow('mailtemplate', 1, 'id')
(row['template']).should.equal('USER_SIGNUP')
(row['subject']).should.equal('new subject')
(row['text1']).should.equal('new text1')
(row['text2']).should.equal('new text2 {{name}}')
(row['text3']).should.equal('new text3 {{url}}/verify-token/{{to}}/{{verificationToken}}')
end
end
describe 'system/recover-mail-template' do