close #15 New account delete option
This commit is contained in:
@@ -14,6 +14,13 @@ use Exception;
|
||||
|
||||
class User extends Controller\Controller{
|
||||
|
||||
/**
|
||||
* valid reasons for captcha images
|
||||
* @var array
|
||||
*/
|
||||
private static $captchaReason = ['createAccount', 'deleteAccount'];
|
||||
|
||||
|
||||
/**
|
||||
* login function
|
||||
* @param $f3
|
||||
@@ -87,20 +94,39 @@ class User extends Controller\Controller{
|
||||
* @param $f3
|
||||
*/
|
||||
public function getCaptcha($f3){
|
||||
$data = $f3->get('POST');
|
||||
|
||||
$img = new \Image();
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
$imgDump = $img->captcha(
|
||||
'fonts/oxygen-bold-webfont.ttf',
|
||||
14,
|
||||
6,
|
||||
'SESSION.captcha_code',
|
||||
'',
|
||||
'0x66C84F',
|
||||
'0x313335'
|
||||
)->dump();
|
||||
// check if reason for captcha generation is valid
|
||||
if(
|
||||
isset($data['reason']) &&
|
||||
in_array( $data['reason'], self::$captchaReason)
|
||||
){
|
||||
$reason = $data['reason'];
|
||||
|
||||
echo $f3->base64( $imgDump, 'image/png');
|
||||
$img = new \Image();
|
||||
|
||||
$imgDump = $img->captcha(
|
||||
'fonts/oxygen-bold-webfont.ttf',
|
||||
14,
|
||||
6,
|
||||
'SESSION.' . $reason,
|
||||
'',
|
||||
'0x66C84F',
|
||||
'0x313335'
|
||||
)->dump();
|
||||
|
||||
$return->img = $f3->base64( $imgDump, 'image/png');
|
||||
}else{
|
||||
$captchaError = (object) [];
|
||||
$captchaError->type = 'error';
|
||||
$captchaError->message = 'Could not create captcha image';
|
||||
$return->error[] = $captchaError;
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,10 +296,10 @@ class User extends Controller\Controller{
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
$captcha = $f3->get('SESSION.captcha_code');
|
||||
$captcha = $f3->get('SESSION.createAccount');
|
||||
|
||||
// reset captcha -> forces user to enter new one
|
||||
$f3->clear('SESSION.captcha_code');
|
||||
$f3->clear('SESSION.createAccount');
|
||||
|
||||
$newUserData = null;
|
||||
|
||||
@@ -598,4 +624,71 @@ class User extends Controller\Controller{
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete current user account from DB
|
||||
* @param $f3
|
||||
*/
|
||||
public function deleteAccount($f3){
|
||||
$data = $f3->get('POST.formData');
|
||||
$return = (object) [];
|
||||
|
||||
$captcha = $f3->get('SESSION.deleteAccount');
|
||||
|
||||
// reset captcha -> forces user to enter new one
|
||||
$f3->clear('SESSION.deleteAccount');
|
||||
|
||||
if(
|
||||
isset($data['captcha']) &&
|
||||
!empty($data['captcha']) &&
|
||||
$data['captcha'] === $captcha
|
||||
){
|
||||
$user = $this->_getUser(0);
|
||||
|
||||
$validUser = $this->_verifyUser( $user->name, $data['password']);
|
||||
|
||||
if(
|
||||
is_object($validUser) &&
|
||||
is_object($user) &&
|
||||
$user->id === $validUser->id
|
||||
){
|
||||
// send delete account mail
|
||||
$msg = 'Hello ' . $user->name . ',<br><br>';
|
||||
$msg .= 'your account data has been successfully deleted.';
|
||||
|
||||
$mailController = new MailController();
|
||||
$status = $mailController->sendDeleteAccount($user->email, $msg);
|
||||
|
||||
if($status){
|
||||
// save log
|
||||
$logText = "id: %s, name: %s, ip: %s";
|
||||
self::getLogger( $this->f3->get('PATHFINDER.LOGFILES.DELETE_ACCOUNT') )->write(
|
||||
sprintf($logText, $user->id, $user->name, $f3->get('IP'))
|
||||
);
|
||||
|
||||
// remove user
|
||||
$user->erase();
|
||||
|
||||
$this->logOut($f3);
|
||||
die();
|
||||
}
|
||||
}else{
|
||||
// password does not match current user pw
|
||||
$passwordError = (object) [];
|
||||
$passwordError->type = 'error';
|
||||
$passwordError->message = 'Invalid password';
|
||||
$return->error[] = $passwordError;
|
||||
}
|
||||
}else{
|
||||
// captcha not valid -> return error
|
||||
$captchaError = (object) [];
|
||||
$captchaError->type = 'error';
|
||||
$captchaError->message = 'Captcha does not match';
|
||||
$return->error[] = $captchaError;
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -183,7 +183,8 @@ class Controller {
|
||||
public function logOut($f3){
|
||||
|
||||
// destroy session
|
||||
$f3->clear('SESSION');
|
||||
$f3->clear('SESSION.user');
|
||||
$f3->sync('SESSION');
|
||||
|
||||
if( !$f3->get('AJAX') ){
|
||||
// redirect to landing page
|
||||
|
||||
@@ -49,7 +49,6 @@ class MailController extends \SMTP{
|
||||
* @return bool
|
||||
*/
|
||||
public function sendInviteKey($to, $msg){
|
||||
|
||||
$this->set('To', '<' . $to . '>');
|
||||
$this->set('From', 'Pathfinder <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
|
||||
$this->set('Subject', 'Registration Key');
|
||||
@@ -57,4 +56,19 @@ class MailController extends \SMTP{
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* send mail to removed user account
|
||||
* @param $to
|
||||
* @param $msg
|
||||
* @return bool
|
||||
*/
|
||||
public function sendDeleteAccount($to, $msg){
|
||||
$this->set('To', '<' . $to . '>');
|
||||
$this->set('From', 'Pathfinder <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
|
||||
$this->set('Subject', 'Account deleted');
|
||||
$status = $this->send($msg);
|
||||
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -34,6 +34,7 @@ app/ui/dialog/manual.js
|
||||
app/ui/dialog/map_settings.js
|
||||
app/ui/dialog/system_effects.js
|
||||
app/ui/dialog/jump_info.js
|
||||
app/ui/dialog/delete_account.js
|
||||
lib/jquery.lazylinepainter-1.5.1.min.js
|
||||
app/ui/logo.js
|
||||
app/ui/dialog/credit.js
|
||||
|
||||
@@ -17,6 +17,7 @@ define(['jquery'], function($) {
|
||||
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
|
||||
saveUserConfig: 'api/user/saveConfig', // ajax URL - saves custom configuration
|
||||
saveSharingConfig: 'api/user/saveSharingConfig', // ajax URL - save "sharing settings" dialog
|
||||
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
|
||||
// access API
|
||||
searchAccess: 'api/access/search', // ajax URL - search user/corporation/ally by name
|
||||
// main config/map ping API
|
||||
|
||||
@@ -21,6 +21,7 @@ define([
|
||||
'dialog/map_settings',
|
||||
'dialog/system_effects',
|
||||
'dialog/jump_info',
|
||||
'dialog/delete_account',
|
||||
'dialog/credit',
|
||||
'slidebars',
|
||||
'app/module_map'
|
||||
@@ -210,6 +211,17 @@ define([
|
||||
).on('click', function(){
|
||||
$(document).triggerMenuEvent('NotificationTest');
|
||||
})
|
||||
).append(
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
href: '#'
|
||||
}).html(' Delete account').prepend(
|
||||
$('<i>',{
|
||||
class: 'fa fa-user-times fa-fw'
|
||||
})
|
||||
).on('click', function(){
|
||||
$(document).triggerMenuEvent('DeleteAccount');
|
||||
})
|
||||
).append(
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
@@ -510,6 +522,12 @@ define([
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('pf:menuDeleteAccount', function(e){
|
||||
// show "delete account" dialog
|
||||
$.fn.showDeleteAccountDialog();
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('pf:menuManual', function(e){
|
||||
// show map manual
|
||||
$.fn.showMapManual();
|
||||
|
||||
@@ -7,7 +7,7 @@ define([
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'bootbox',
|
||||
'bootbox'
|
||||
], function($, Init, Util, Render, bootbox) {
|
||||
'use strict';
|
||||
|
||||
@@ -40,7 +40,7 @@ define([
|
||||
};
|
||||
|
||||
/**
|
||||
* getz active Tab link element for a dialog
|
||||
* get active Tab link element for a dialog
|
||||
* @param dialog
|
||||
* @returns {JQuery|*}
|
||||
*/
|
||||
@@ -51,55 +51,6 @@ define([
|
||||
return currentActiveTab;
|
||||
};
|
||||
|
||||
/**
|
||||
* generates a captcha image and return as base64 image/png
|
||||
* @param callback
|
||||
*/
|
||||
var getCaptchaImage = function(callback){
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.getCaptcha,
|
||||
data: {},
|
||||
dataType: 'text'
|
||||
}).done(function(base64Image){
|
||||
|
||||
callback(base64Image);
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
var reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': saveConfig', text: reason, type: 'warning'});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* clear a field and reset success/error classes
|
||||
* @param fieldId
|
||||
*/
|
||||
var resetFormField = function(fieldId){
|
||||
var field = $('#' + fieldId);
|
||||
field.val('');
|
||||
field.parents('.form-group').removeClass('has-error has-success');
|
||||
}
|
||||
|
||||
/**
|
||||
* request captcha image and show in form
|
||||
*/
|
||||
var showCaptchaImage = function(){
|
||||
|
||||
var captchaWrapper = $('#' + config.captchaImageWrapperId);
|
||||
var captchaImage = $('#' + config.captchaImageId);
|
||||
|
||||
captchaWrapper.showLoadingAnimation(config.loadingOptions);
|
||||
getCaptchaImage(function(base64Image){
|
||||
|
||||
captchaImage.attr('src', base64Image).show();
|
||||
captchaWrapper.hideLoadingAnimation();
|
||||
|
||||
// reset captcha field
|
||||
resetFormField('captcha');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init popovers in dialog
|
||||
* @param dialogElement
|
||||
@@ -127,7 +78,7 @@ define([
|
||||
var cloneRow = dialogElement.find('.' + config.settingsCloneApiRowClass).last();
|
||||
var newApiRow = cloneRow.clone();
|
||||
|
||||
newApiRow.find('.form-group').removeClass('has-success has-error')
|
||||
newApiRow.find('.form-group').removeClass('has-success has-error');
|
||||
newApiRow.find('input').val('');
|
||||
cloneRow.after(newApiRow);
|
||||
|
||||
@@ -311,7 +262,9 @@ define([
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
showCaptchaImage();
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
}else{
|
||||
// store new/updated user data -> update head
|
||||
if(responseData.userData){
|
||||
@@ -329,7 +282,9 @@ define([
|
||||
// switch tab
|
||||
changeTab();
|
||||
|
||||
showCaptchaImage();
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -344,7 +299,9 @@ define([
|
||||
|
||||
// set new captcha for any request
|
||||
// captcha is required for sensitive data (not for all)
|
||||
showCaptchaImage();
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
|
||||
// check for DB errors
|
||||
if(jqXHR.status === 500){
|
||||
@@ -400,7 +357,7 @@ define([
|
||||
var form = dialogElement.find('form');
|
||||
|
||||
// request captcha image and show
|
||||
showCaptchaImage();
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount');
|
||||
|
||||
// init dialog tooltips
|
||||
dialogElement.initTooltips();
|
||||
|
||||
115
js/app/ui/dialog/delete_account.js
Normal file
115
js/app/ui/dialog/delete_account.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* delete account dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], function($, Init, Util, bootbox) {
|
||||
'use strict';
|
||||
|
||||
var config = {
|
||||
// global dialog
|
||||
deleteAccountId: 'pf-dialog-delete-account', // dialog id
|
||||
|
||||
// captcha
|
||||
captchaImageWrapperId: 'pf-dialog-captcha-wrapper' // id for "captcha image" wrapper
|
||||
};
|
||||
|
||||
/**
|
||||
* shows delete account dialog
|
||||
*/
|
||||
$.fn.showDeleteAccountDialog = function(){
|
||||
|
||||
|
||||
requirejs(['text!templates/dialog/delete_account.html', 'mustache'], function(template, Mustache) {
|
||||
|
||||
var data = {
|
||||
deleteAccountId: config.deleteAccountId,
|
||||
userData: Util.getCurrentUserData(),
|
||||
captchaImageWrapperId: config.captchaImageWrapperId,
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass
|
||||
};
|
||||
|
||||
var content = Mustache.render(template, data);
|
||||
|
||||
var deleteAccountDialog = bootbox.dialog({
|
||||
title: 'Delete account',
|
||||
message: content,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fa fa-user-times fa-fw"></i> delete account',
|
||||
className: 'btn-danger',
|
||||
callback: function() {
|
||||
var dialogElement = $(this);
|
||||
var form = dialogElement.find('form');
|
||||
|
||||
// validate form
|
||||
form.validator('validate');
|
||||
var formValid = form.isValidForm();
|
||||
|
||||
if(formValid){
|
||||
|
||||
var formValues = form.getFormValues();
|
||||
|
||||
if(! $.isEmptyObject(formValues) ){
|
||||
// send Tab data and store values
|
||||
var requestData = {
|
||||
formData: formValues
|
||||
};
|
||||
|
||||
dialogElement.find('.modal-content').showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.deleteAccount,
|
||||
data: requestData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
dialogElement.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
if(responseData.reroute !== undefined){
|
||||
Util.redirect(responseData.reroute, []);
|
||||
}else if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('deleteAccount', function(){
|
||||
form.find('[name="captcha"], [name="password"]').resetFormFields();
|
||||
});
|
||||
}
|
||||
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
dialogElement.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
var reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': deleteAccount', text: reason, type: 'error'});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// after modal is shown =======================================================================
|
||||
deleteAccountDialog.on('shown.bs.modal', function(e) {
|
||||
// request captcha image and show
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage('deleteAccount');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
});
|
||||
@@ -137,6 +137,73 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show a unique generated captcha image
|
||||
* @param reason
|
||||
* @param callback
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.showCaptchaImage = function(reason, callback){
|
||||
return this.each(function(){
|
||||
var captchaWrapper = $(this);
|
||||
var captchaImage = captchaWrapper.find('img');
|
||||
|
||||
captchaWrapper.showLoadingAnimation(config.loadingOptions);
|
||||
getCaptchaImage(reason, function(base64Image){
|
||||
|
||||
captchaImage.attr('src', base64Image).show();
|
||||
captchaWrapper.hideLoadingAnimation({ // config for loading overlay
|
||||
icon: {
|
||||
size: 'fa-2x'
|
||||
}
|
||||
});
|
||||
|
||||
if(callback){
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* request a captcha image
|
||||
* @param callback
|
||||
*/
|
||||
var getCaptchaImage = function(reason, callback){
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.getCaptcha,
|
||||
data: {
|
||||
reason: reason
|
||||
},
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
if(responseData.error.length > 0){
|
||||
showNotify({title: 'getCaptchaImage', text: 'Captcha image gneration failed', type: 'error'});
|
||||
}else{
|
||||
callback(responseData.img);
|
||||
}
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
var reason = status + ' ' + error;
|
||||
showNotify({title: jqXHR.status + ': getCaptchaImage', text: reason, type: 'error'});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* reset/clear form fields
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.resetFormFields = function(){
|
||||
return this.each(function(){
|
||||
var field = $(this);
|
||||
|
||||
field.val('');
|
||||
field.parents('.form-group').removeClass('has-error has-success');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show form messages
|
||||
* check: showMessage() for en other way of showing messages
|
||||
@@ -1433,6 +1500,22 @@ define([
|
||||
return dateString + ' ' + timeString;
|
||||
};
|
||||
|
||||
/**
|
||||
* redirect
|
||||
* @param url
|
||||
* @param params
|
||||
*/
|
||||
var redirect = function(url, params){
|
||||
var currentUrl = document.URL;
|
||||
|
||||
if(url !== currentUrl){
|
||||
if(params.length > 0){
|
||||
url += '?' + params.join('&');
|
||||
}
|
||||
window.location = url;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* send logout request
|
||||
*/
|
||||
@@ -1444,15 +1527,8 @@ define([
|
||||
data: {},
|
||||
dataType: 'json'
|
||||
}).done(function(data){
|
||||
|
||||
if(data.reroute !== undefined){
|
||||
var landingPageUrl = data.reroute;
|
||||
var currentUrl = document.URL;
|
||||
|
||||
// relocate to landing page
|
||||
if(landingPageUrl !== currentUrl){
|
||||
window.location = landingPageUrl + '?logout';
|
||||
}
|
||||
redirect(data.reroute, ['logout']);
|
||||
}
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
|
||||
@@ -1504,6 +1580,7 @@ define([
|
||||
getCurrentCharacterLog: getCurrentCharacterLog,
|
||||
convertDateToString: convertDateToString,
|
||||
formatPrice: formatPrice,
|
||||
redirect: redirect,
|
||||
logout: logout
|
||||
};
|
||||
});
|
||||
75
public/templates/dialog/delete_account.html
Normal file
75
public/templates/dialog/delete_account.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<div class="row" id="{{deleteAccountId}}">
|
||||
<div class="col-sm-12">
|
||||
<form role="form" class="form-horizontal">
|
||||
|
||||
{{#userData.name}}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Username</label>
|
||||
<div class="col-sm-9">
|
||||
<p class="form-control-static">{{userData.name}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/userData.name}}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="password" class="col-sm-3 control-label">Password</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group" title="Enter your password" data-placement="right">
|
||||
<input name="password" type="password" class="form-control" id="password" placeholder="" data-error="Field is required" autocomplete="off" required>
|
||||
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
|
||||
</div>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label"></label>
|
||||
<div class="col-sm-6">
|
||||
<p id="{{captchaImageWrapperId}}">
|
||||
<img src="" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="captcha" class="col-sm-3 control-label">Captcha</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group" title="Enter the characters seen above" data-placement="right">
|
||||
<input name="captcha" type="text" class="form-control" id="captcha" placeholder="" data-minlength="6" data-minlength-error="Min. of 6 characters" data-error="Field is required" autocomplete="off" required>
|
||||
<span class="input-group-addon"><i class="fa fa-fw fa-refresh"></i></span>
|
||||
</div>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<span class="txt-color txt-color-warning">Delete account</span>
|
||||
<small>This will permanently remove your account!</small>
|
||||
</div>
|
||||
|
||||
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
|
||||
<span class="txt-color txt-color-danger">Error</span>
|
||||
<small> (important non-critical information)</small>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user