Browse Source

UI-3269: Update "add user" form and add new section "add device" (#109)

* Remove this

* Update add user modal to show labels instead of icons

* Fill brand field on add device section

* add device form

* Save user device

* Binding form events from single function

* Add extra button Create User and Add Device

* Code style

* Create user and device form based on mockup

* Handling form validation

* Create User and Add another button

* Revert "Remove this"

This reverts commit 7102cf9b4a.

* Styled notification email

* Use single chosen field to select device model

* improvements

* Improve form validation message

* Delete unused template

* Code style improvements

* Fix typo on lodash flatMap statement
Device family as argument

* code style improvements

* Create function to render add user modal

* Handle error on user creation to prevent form buttons state

* Render users list without auto scroll when save and new btn is clicked

* Fix device data validation
4.3
Ricardo Merino 7 years ago
committed by GitHub
parent
commit
cb785bdc83
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 464 additions and 205 deletions
  1. +17
    -3
      i18n/en-US.json
  2. +277
    -106
      submodules/users/users.js
  3. +64
    -27
      submodules/users/users.scss
  4. +106
    -69
      submodules/users/views/creation.html

+ 17
- 3
i18n/en-US.json View File

@ -483,6 +483,7 @@
},
"dialogCreationUser": {
"createUser": "Create User",
"createUserAndAddAnother": "Create User and Add Another",
"createVmbox": "Create a voicemail box for this user",
"errorOnCreation": "An error occured while trying to create this user:",
"extension": "Main Extension Number",
@ -491,14 +492,27 @@
"lastName": "Last Name",
"loginEmail": "Email used for Login",
"notificationEmail": "Notification E-mail",
"password": "Password (Min. 6 characters)",
"password": "Password",
"passwordPlaceholder": "Password (Min. 6 characters)",
"sendToDifferentEmail": "Send emails to an alternate address",
"title": "Create User",
"title": "Add User and Device",
"vmboxAlreadyExist": "VMBox Number is already taken, please choose a different Voicemail Box Number.",
"vmboxNumber": "VM Box #",
"__comment": "UI-814: Adding the option to automatically send email on user creation",
"__version": "3.20_s3",
"sendWelcomeEmail": "Send credentials to this user"
"sendWelcomeEmail": "Send credentials to this user",
"addDevice": {
"deviceMake": {
"label": "Device Make",
"none": "None"
},
"deviceModel": {
"label": "Device Model"
},
"macAddress": "MAC Address",
"name": "Device Name",
"title": "Add Device"
}
},
"editionForm": {
"changePIN": "Change PIN",


+ 277
- 106
submodules/users/users.js View File

@ -6,7 +6,14 @@ define(function(require) {
var app = {
requests: {},
requests: {
/* Provisioner */
'common.chooseModel.getProvisionerData': {
apiRoot: monster.config.api.provisioner,
url: 'phones',
verb: 'GET'
}
},
subscribe: {
'voip.users.render': 'usersRender'
@ -677,106 +684,7 @@ define(function(require) {
});
template.find('.users-header .add-user').on('click', function() {
monster.parallel({
callflows: function(callback) {
self.usersListCallflows(function(callflows) {
callback(null, callflows);
});
},
vmboxes: function(callback) {
self.usersListVMBoxes({
success: function(vmboxes) {
callback(null, vmboxes);
}
});
}
}, function(err, results) {
var originalData = self.usersFormatAddUser(results),
userTemplate = $(self.getTemplate({
name: 'creation',
data: originalData,
submodule: 'users'
})),
userCreationForm = userTemplate.find('#form_user_creation'),
validationOptions = {
ignore: ':hidden:not(select)',
rules: {
'callflow.extension': {
checkList: originalData.listExtensions
},
'vmbox.number': {
checkList: originalData.listVMBoxes
},
'user.password': {
minlength: 6
}
},
messages: {
'user.first_name': {
required: self.i18n.active().validation.required
},
'user.last_name': {
required: self.i18n.active().validation.required
},
'callflow.extension': {
required: self.i18n.active().validation.required
}
}
};
if (originalData.licensedUserRoles) {
validationOptions.rules['user.extra.licensedRole'] = {
checkList: [ 'none' ]
};
validationOptions.messages['user.extra.licensedRole'] = {
checkList: self.i18n.active().validation.required
};
}
monster.ui.mask(userTemplate.find('#extension'), 'extension');
monster.ui.chosen(userTemplate.find('#licensed_role'));
monster.ui.validate(userCreationForm, validationOptions);
// Force select element validation on change event
// (Not handled by jQuery Validation plugin because the select
// element is hidden by the Chosen jQuery plugin. For more info, see:
// https://github.com/jquery-validation/jquery-validation/issues/997)
userCreationForm.find('select').on('change', function() {
userCreationForm.validate().element(this);
});
monster.ui.showPasswordStrength(userTemplate.find('#password'));
userTemplate.find('#create_user').on('click', function() {
if (monster.ui.valid(userTemplate.find('#form_user_creation'))) {
var $this = $(this),
dataForm = monster.ui.getFormData('form_user_creation'),
formattedData = self.usersFormatCreationData(dataForm);
$this
.prop('disabled', true);
self.usersCreate(formattedData, function(data) {
popup.dialog('close').remove();
self.usersRender({ userId: data.user.id });
}, function() {
$this
.prop('disabled', false);
});
}
});
userTemplate.find('#notification_email').on('change', function() {
userTemplate.find('.email-group').toggleClass('hidden');
});
var popup = monster.ui.dialog(userTemplate, {
title: self.i18n.active().users.dialogCreationUser.title
});
});
self.usersRenderAddModalDialog();
});
template.on('click', '.cancel-link', function() {
@ -1738,6 +1646,198 @@ define(function(require) {
});
},
usersBindAddUserEvents: function(args) {
var self = this,
template = args.template,
data = args.data,
popup = args.popup;
template.find('.create_user').on('click', function() {
var action = $(this).data('action');
if (monster.ui.valid(template.find('#form_user_creation'))) {
var $buttons = template.find('.create_user'),
dataForm = _.merge(monster.ui.getFormData('form_user_creation'), {
user: {
device: {
family: template.find('#device_model').find(':selected').data('family')
}
}
}),
formattedData = self.usersFormatCreationData(dataForm);
$buttons.prop('disabled', true);
self.usersCreate(formattedData, function(data) {
popup.dialog('close').remove();
switch (action) {
case 'add_new':
self.usersRender();
self.usersRenderAddModalDialog();
break;
default:
self.usersRender({ userId: data.user.id });
break;
}
}, function() {
$buttons.prop('disabled', false);
});
}
});
template.find('#notification_email').on('change', function() {
template.find('.email-group').toggleClass('hidden');
});
template.find('#device_brand').on('change', function() {
var brand = $(this).val(),
selectedBrand = [],
$deviceModel = template.find('.device-model'),
$deviceName = template.find('.device-name'),
$deviceMac = template.find('.device-mac'),
$deviceModelSelect = template.find('#device_model');
if (brand !== 'none') {
self.usersDeviceFormReset(template);
$deviceModel.slideDown();
$deviceName.slideDown();
$deviceMac.slideDown();
selectedBrand = _.find(data.listProvisioners, function(provisioner) {
return provisioner.name === brand;
});
$deviceModelSelect
.find('option')
.remove()
.end();
selectedBrand.models.map(function(model) {
var option = $('<option>', {
value: model.name,
text: model.name
}).attr('data-family', model.family);
$deviceModelSelect.append(option);
});
$deviceModelSelect.trigger('chosen:updated');
return;
}
self.usersDeviceFormReset(template);
});
},
usersRenderAddModalDialog: function() {
var self = this;
monster.parallel({
callflows: function(callback) {
self.usersListCallflows(function(callflows) {
callback(null, callflows);
});
},
vmboxes: function(callback) {
self.usersListVMBoxes({
success: function(vmboxes) {
callback(null, vmboxes);
}
});
},
provisioners: function(callback) {
monster.request({
resource: 'common.chooseModel.getProvisionerData',
data: {},
success: function(provisionerData) {
callback(null, provisionerData.data);
}
});
}
}, function(err, results) {
var originalData = self.usersFormatAddUser(results),
userTemplate = $(self.getTemplate({
name: 'creation',
data: originalData,
submodule: 'users'
})),
userCreationForm = userTemplate.find('#form_user_creation'),
validationOptions = {
ignore: ':hidden:not(select)',
rules: {
'callflow.extension': {
checkList: originalData.listExtensions
},
'vmbox.number': {
checkList: originalData.listVMBoxes
},
'user.password': {
minlength: 6
}
},
messages: {
'user.first_name': {
required: self.i18n.active().validation.required
},
'user.last_name': {
required: self.i18n.active().validation.required
},
'callflow.extension': {
required: self.i18n.active().validation.required
}
}
};
if (originalData.licensedUserRoles) {
validationOptions.rules['user.extra.licensedRole'] = {
checkList: [ 'none' ]
};
validationOptions.messages['user.extra.licensedRole'] = {
checkList: self.i18n.active().validation.required
};
}
monster.ui.mask(userTemplate.find('#extension'), 'extension');
monster.ui.chosen(userTemplate.find('#licensed_role'));
monster.ui.mask(userTemplate.find('#mac_address'), 'macAddress');
monster.ui.validate(userCreationForm, validationOptions);
// Force select element validation on change event
// (Not handled by jQuery Validation plugin because the select
// element is hidden by the Chosen jQuery plugin. For more info, see:
// https://github.com/jquery-validation/jquery-validation/issues/997)
userCreationForm.find('select').on('change', function() {
userCreationForm.validate().element(this);
});
monster.ui.showPasswordStrength(userTemplate.find('#password'));
monster.ui.chosen(userTemplate.find('#device_brand'));
monster.ui.chosen(userTemplate.find('#device_model'));
var popup = monster.ui.dialog(userTemplate, {
title: self.i18n.active().users.dialogCreationUser.title
});
self.usersBindAddUserEvents({
template: userTemplate,
data: originalData,
popup: popup
});
});
},
usersDeviceFormReset: function(template) {
var $deviceModel = template.find('.device-model'),
$deviceName = template.find('.device-name'),
$deviceMac = template.find('.device-mac');
$deviceModel.slideUp();
$deviceName.slideUp();
$deviceMac.slideUp();
},
usersGetCallRecordingData: function(userId, globalCallback) {
var self = this;
@ -1883,6 +1983,20 @@ define(function(require) {
// If for some reason a vmbox number exist without an extension, we still don't want to let them set their extension number to that number.
allNumbers = arrayExtensions.concat(arrayVMBoxes);
formattedData.nextExtension = parseInt(monster.util.getNextExtension(allNumbers)) + '';
formattedData.listProvisioners = _.map(data.provisioners, function(brand) {
var models = _.flatMap(brand.families, function(family) {
return _.map(family.models, function(model) {
model.family = family.name;
return model;
});
});
return {
id: brand.id,
name: brand.name,
models: models
};
});
return formattedData;
},
@ -3720,6 +3834,35 @@ define(function(require) {
delete formattedData.user.extra;
if (
_.get(data, 'user.device.brand', 'none') === 'none'
&& _.get(data, 'user.device.model', 'none') === 'none'
&& _.isEmpty(_.get(data, 'user.device.name'))
&& _.isEmpty(_.get(data, 'user.device.mac_address'))
) {
delete formattedData.user.device;
return formattedData;
}
formattedData.user.device = {
device_type: 'sip_device',
enabled: true,
mac_address: data.user.device.mac_address,
name: data.user.device.name,
provision: {
endpoint_brand: data.user.device.brand,
endpoint_family: data.user.device.family,
endpoint_model: data.user.device.model
},
sip: {
password: monster.util.randomString(12),
realm: monster.apps.auth.currentAccount.realm,
username: 'user_' + monster.util.randomString(10)
},
suppress_unregister_notifications: false,
family: data.user.device.family
};
return formattedData;
},
@ -3781,6 +3924,11 @@ define(function(require) {
callback(null, _dataUser);
},
error: function(parsedError) {
if (parsedError.error === '402') {
error();
return;
}
callback(true);
},
onChargesCancelled: function() {
@ -3817,11 +3965,23 @@ define(function(require) {
},
function(_dataUser, _dataCF, callback) {
if (!data.extra.includeInDirectory) {
callback(null);
callback(null, _dataUser);
return;
}
self.usersAddUserToMainDirectory(_dataUser, _dataCF.id, function(dataDirectory) {
callback(null, _dataUser);
});
},
function(_dataUser, callback) {
if (!data.user.device) {
callback(null);
return;
}
var deviceData = data.user.device;
deviceData.owner_id = _dataUser.id;
self.usersAddUserDevice(deviceData, function(_device) {
callback(null);
});
}
@ -3831,6 +3991,7 @@ define(function(require) {
error();
return;
}
success(data);
});
},
@ -4031,6 +4192,20 @@ define(function(require) {
});
},
usersAddUserDevice: function(dataDevice, callback) {
var self = this;
self.callApi({
resource: 'device.create',
data: {
accountId: self.accountId,
data: dataDevice
},
success: function(device) {
callback && callback(device);
}
});
},
usersGetMainCallflow: function(userId, callback) {
var self = this;
@ -5206,10 +5381,6 @@ define(function(require) {
args.hasOwnProperty('success') && args.success(data.data);
},
error: function(parsedError) {
if (parsedError.error === '402') {
return;
}
args.hasOwnProperty('error') && args.error(parsedError);
},
onChargesCancelled: function() {


+ 64
- 27
submodules/users/users.scss View File

@ -591,10 +591,70 @@
background: #FFF;
/* box-shadow: 1px 1px 15px #ddd inset;*/
}
#creation_user_dialog form > div {
padding: 20px 40px 0px 60px;
border-bottom: 1px solid #c1c1c1;
#creation_user_dialog {
width: 600px;
padding: 10px;
#form_user_creation {
margin: 0;
}
.row-fluid {
margin-bottom: 8px;
&.user-section-title {
h3 {
padding-left: 30px;
font-weight: normal;
font-size: 20px;
margin: 0;
}
}
&.separator {
border-bottom: 1px solid #c1c1c1;
margin: 20px 0;
}
.chosen-drop {
top: auto !important;
bottom: 100% !important;
}
.control-label {
margin: 0;
text-align: left;
}
input {
margin: 0;
}
.user-email-icon {
margin-right: 10px;
}
.monster-password-strength {
width: 220px;
}
div.to-left {
padding-left: 50px;
}
div.to-right {
padding-left: 20px;
}
.device-model,
.device-name,
.device-mac {
display: none;
}
}
.user-options-area {
padding-left: 150px;
.email-group {
padding-left: 10px;
#email {
margin-left: 10px;
}
}
}
.add-user-actions {
margin-top: 30px;
button:not(:first-child) {
margin-left: 10px;
}
}
}
#creation_user_dialog i {
@ -606,10 +666,6 @@
text-align: center;
}
#creation_user_dialog .control-group .controls {
margin-left: 36px;
}
#creation_user_dialog input.same-line {
margin-left: 6px;
}
@ -624,14 +680,6 @@
}
#creation_user_dialog label.monster-invalid{
&[for="user.last_name"] {
margin-left: 113px;
}
&[for="extension"] {
margin-left: 160px;
}
&[for="password"] {
margin-top: 25px;
}
@ -677,17 +725,6 @@
width: 74px;
}
#creation_user_dialog #extension {
width: 47px;
margin-left: 5px;
}
#creation_user_dialog .extension-label {
display: inline-block;
white-space: nowrap;
width: 153px;
}
/* Change Password Popup */
.change-password-popup label.monster-invalid[for="inputPassword"] {
margin-top: 25px;


+ 106
- 69
submodules/users/views/creation.html View File

@ -1,34 +1,38 @@
<div id="creation_user_dialog">
<form class="form-horizontal small-labels" id="form_user_creation">
<div>
<div class="control-group">
<label class="control-label" for="first_name"><i class="fa fa-user"></i></label>
<div class="controls">
<input required class="input-small" type="text" id="first_name" name="user.first_name" placeholder="{{i18n.users.dialogCreationUser.firstName}}"></input>
<input required class="same-line input-small" type="text" name="user.last_name" placeholder="{{i18n.users.dialogCreationUser.lastName}}"></input>
</div>
<form class="form-horizontal clearfix" id="form_user_creation">
<div class="row-fluid user-section-title">
<h3>{{i18n.users.dialogCreationUser.createUser}}</h3>
</div>
<div class="control-group">
<label class="control-label" for="email"><i class="fa fa-key"></i></label>
<div class="controls">
<div class="row-fluid">
<div class="span6 to-left">
<label class="control-label" for="first_name">{{i18n.users.dialogCreationUser.firstName}}</label>
<input required type="text" id="first_name" name="user.first_name" placeholder="{{i18n.users.dialogCreationUser.firstName}}" />
</div>
<div class="span6 to-right">
<label class="control-label" for="username">{{i18n.users.dialogCreationUser.loginEmail}}</label>
<input required type="email" name="user.username" id="username" placeholder="{{i18n.users.dialogCreationUser.loginEmail}}"/>
</div>
</div>
{{#unless _whitelabel.hide_user_passwords}}
<div class="control-group">
<label class="control-label" for="password"><i class="fa fa-unlock"></i></label>
<div class="controls">
<input required type="password" name="user.password" id="password" placeholder="{{i18n.users.dialogCreationUser.password}}"/>
<div class="row-fluid">
<div class="span6 to-left">
<label class="control-label" for="last_name">{{i18n.users.dialogCreationUser.lastName}}</label>
<input required type="text" id="last_name" name="user.last_name" placeholder="{{i18n.users.dialogCreationUser.lastName}}" />
</div>
{{#unless _whitelabel.hide_user_passwords}}
<div class="span6 to-right">
<label class="control-label" for="password">{{i18n.users.dialogCreationUser.password}}</label>
<input required type="password" name="user.password" id="password" placeholder="{{i18n.users.dialogCreationUser.passwordPlaceholder}}"/>
</div>
{{/unless}}
</div>
<div class="row-fluid">
<div class="span6 to-left">
<label class="control-label" for="extension">{{ i18n.users.dialogCreationUser.extension }}</label>
<input required type="text" maxlength="6" name="callflow.extension" id="extension" value="{{nextExtension}}"/>
</div>
{{/unless}}
{{#if licensedUserRoles}}
<div class="control-group">
<label for="licensed_role" class="control-label"><i class="fa fa-user"></i></label>
<div class="controls">
{{#if licensedUserRoles}}
<div class="span6 to-right">
<label class="control-label" for="licensed_role">{{ i18n.users.licensedUserRoles.selectPlaceholder }}</label>
<select id="licensed_role" name="user.extra.licensedRole" data-placeholder="{{ i18n.users.licensedUserRoles.selectPlaceholder }}">
<option value="none">
{{ i18n.users.licensedUserRoles.licensedUserRoles.none }}
@ -38,62 +42,95 @@
{{/each}}
</select>
</div>
</div>
{{/if}}
</div>
{{/if}}
</div>
<div>
<div class="control-group">
<label class="control-label" for="extension"><i class="icon-telicon-extensions"></i></label>
<div class="controls">
<span class="extension-label">{{ i18n.users.dialogCreationUser.extension }}</span>
<input required maxlength="6" type="text" name="callflow.extension" id="extension" value="{{nextExtension}}"/>
<div class="row-fluid separator"></div>
<div class="user-options-area">
<div class="control-group hack-left">
<label class="control-label" for="create_vmbox"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.createVmbox }}
<input id="create_vmbox" type="checkbox" name="extra.createVmbox"{{#if createVmbox}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</div>
</div>
<div class="control-group hack-left">
<label class="control-label" for="include_directory"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.includeInDirectory }}
<input id="include_directory" type="checkbox" name="extra.includeInDirectory"{{#if includeInDirectory}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</div>
</div>
<div class="control-group hack-left">
<label class="control-label" for="notification_email"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendToDifferentEmail }}
<input id="notification_email" type="checkbox" name="extra.differentEmail"{{#if sendToDifferentEmail}} checked{{/if}}></input>
{{/monsterCheckbox}}
</div>
</div>
<div class="control-group email-group{{#unless sendToDifferentEmail}} hidden{{/unless}}">
<i class="fa fa-envelope"></i>
<input type="email" name="extra.email" id="email" placeholder="{{i18n.users.dialogCreationUser.notificationEmail}}">
</div>
<div class="control-group hack-left">
<label class="control-label" for="send_email_on_creation"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendWelcomeEmail }}
<input id="send_email_on_creation" type="checkbox" name="user.send_email_on_creation"></input>
{{/monsterCheckbox}}
</div>
</div>
</div>
</div>
<div>
<div class="control-group hack-left">
<label class="control-label" for="create_vmbox"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.createVmbox }}
<input id="create_vmbox" type="checkbox" name="extra.createVmbox"{{#if createVmbox}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</div>
<div class="row-fluid separator"></div>
<div class="row-fluid user-section-title">
<h3>{{ i18n.users.dialogCreationUser.addDevice.title }}</h3>
</div>
<div class="control-group hack-left">
<label class="control-label" for="include_directory"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.includeInDirectory }}
<input id="include_directory" type="checkbox" name="extra.includeInDirectory"{{#if includeInDirectory}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
<div class="row-fluid">
<div class="span6 to-left">
<label class="control-label" for="device_brand">{{ i18n.users.dialogCreationUser.addDevice.deviceMake.label }}</i></label>
<select id="device_brand" name="user.device.brand">
<option value="none">
{{ i18n.users.dialogCreationUser.addDevice.deviceMake.none }}
</option>
{{#each listProvisioners}}
<option value="{{name}}">{{name}}</option>
{{/each}}
</select>
</div>
</div>
<div class="control-group hack-left">
<label class="control-label" for="notification_email"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendToDifferentEmail }}
<input id="notification_email" type="checkbox" name="extra.differentEmail"{{#if sendToDifferentEmail}} checked{{/if}}></input>
{{/monsterCheckbox}}
<div class="span6 device-name to-right">
<label for="device-name" class="control-label">{{ i18n.users.dialogCreationUser.addDevice.name }}</label>
<input required type="text" id="device-name" name="user.device.name" placeholder="{{ i18n.users.dialogCreationUser.addDevice.name }}">
</div>
</div>
<div class="control-group email-group{{#unless sendToDifferentEmail}} hidden{{/unless}}">
<label class="control-label" for="email"><i class="fa fa-envelope"></i></label>
<div class="controls">
<input type="email" name="extra.email" id="email" placeholder="{{i18n.users.dialogCreationUser.notificationEmail}}">
<div class="row-fluid">
<div class="span6 to-left device-model">
<label class="control-label" for="device_model">{{ i18n.users.dialogCreationUser.addDevice.deviceModel.label }}</label>
<select id="device_model" name="user.device.model">
<option value="none">
{{ i18n.users.dialogCreationUser.addDevice.deviceMake.none }}
</option>
</select>
</div>
<div class="span6 device-mac to-right">
<label for="mac_address" class="control-label">{{ i18n.users.dialogCreationUser.addDevice.macAddress }}</label>
<input required type="text" id="mac_address" name="user.device.mac_address" placeholder="19:33:1A:B2:12:58">
</div>
</div>
<div class="control-group hack-left">
<label class="control-label" for="send_email_on_creation"></label>
<div class="controls">
{{#monsterCheckbox i18n.users.dialogCreationUser.sendWelcomeEmail }}
<input id="send_email_on_creation" type="checkbox" name="user.send_email_on_creation"></input>
{{/monsterCheckbox}}
<div>
<div class="dialog-buttons-wrapper add-user-actions">
<button class="monster-button monster-button-success create_user">{{ i18n.users.dialogCreationUser.createUser }}</button>
<button class="monster-button monster-button-success create_user" data-action="add_new">{{ i18n.users.dialogCreationUser.createUserAndAddAnother }}</button>
</div>
</div>
</div>
</form>
<div class="dialog-buttons-wrapper">
<button id="create_user" class="monster-button monster-button-success">{{ i18n.users.dialogCreationUser.createUser }}</button>
</div>
</div>

Loading…
Cancel
Save