1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-21 14:19:43 +02:00

user admin UX

This commit is contained in:
Harvey Kandola 2017-11-29 10:31:00 +00:00
parent 79531e01b3
commit 95bb440a65
9 changed files with 194 additions and 315 deletions

View file

@ -9,9 +9,8 @@
// //
// https://documize.com // https://documize.com
import { debounce } from '@ember/runloop';
import Component from '@ember/component'; import Component from '@ember/component';
import { schedule, debounce } from '@ember/runloop';
import AuthProvider from '../../mixins/auth'; import AuthProvider from '../../mixins/auth';
import DropdownMixin from '../../mixins/dropdown'; import DropdownMixin from '../../mixins/dropdown';
@ -24,6 +23,7 @@ export default Component.extend(AuthProvider, DropdownMixin, {
filteredUsers: [], filteredUsers: [],
selectedUsers: [], selectedUsers: [],
hasSelectedUsers: false, hasSelectedUsers: false,
showDeleteDialog: false,
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
@ -39,11 +39,6 @@ export default Component.extend(AuthProvider, DropdownMixin, {
this.set('filteredUsers', users); this.set('filteredUsers', users);
}, },
willDestroyElement() {
this._super(...arguments);
this.destroyDropdown();
},
onKeywordChange: function () { onKeywordChange: function () {
debounce(this, this.filterUsers, 350); debounce(this, this.filterUsers, 350);
}.observes('filter'), }.observes('filter'),
@ -101,76 +96,45 @@ export default Component.extend(AuthProvider, DropdownMixin, {
this.attrs.onSave(user); this.attrs.onSave(user);
}, },
edit(id) { onShowEdit(id) {
let self = this;
let user = this.users.findBy("id", id); let user = this.users.findBy("id", id);
let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'accounts'); let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'accounts');
this.set('editUser', userCopy); this.set('editUser', userCopy);
this.set('password', { this.set('password', {
password: "", password: "",
confirmation: "" confirmation: ""
}); });
$(".edit-user-dialog").css("display", "block");
$("input").removeClass("error");
this.closeDropdown(); $('#edit-user-modal').on('show.bs.modal', function(event) { // eslint-disable-line no-unused-vars
schedule('afterRender', () => {
let dropOptions = Object.assign(this.get('dropDefaults'), { $("#edit-firstname").focus();
target: $(".edit-button-" + id)[0],
content: $(".edit-user-dialog")[0],
classes: 'drop-theme-basic',
position: "bottom right",
remove: false});
let drop = new Drop(dropOptions);
self.set('dropdown', drop);
drop.on('open', function () {
self.$("#edit-firstname").focus();
}); });
});
$('#edit-user-modal').modal('dispose');
$('#edit-user-modal').modal({show: true});
}, },
confirmDelete(id) { onUpdate() {
let user = this.users.findBy("id", id);
this.set('deleteUser', user);
$(".delete-user-dialog").css("display", "block");
this.closeDropdown();
let dropOptions = Object.assign(this.get('dropDefaults'), {
target: $(".delete-button-" + id)[0],
content: $(".delete-user-dialog")[0],
classes: 'drop-theme-basic',
position: "bottom right",
remove: false});
let drop = new Drop(dropOptions);
this.set('dropdown', drop);
},
cancel() {
this.closeDropdown();
},
save() {
let user = this.get('editUser'); let user = this.get('editUser');
let password = this.get('password'); let password = this.get('password');
if (is.empty(user.firstname)) { if (is.empty(user.firstname)) {
$("#edit-firstname").addClass("error").focus(); $("#edit-firstname").addClass("is-invalid").focus();
return; return;
} }
if (is.empty(user.lastname)) { if (is.empty(user.lastname)) {
$("#edit-lastname").addClass("error").focus(); $("#edit-lastname").addClass("is-invalid").focus();
return; return;
} }
if (is.empty(user.email)) { if (is.empty(user.email) || is.not.email(user.email)) {
$("#edit-email").addClass("error").focus(); $("#edit-email").addClass("is-invalid").focus();
return; return;
} }
this.closeDropdown(); $('#edit-user-modal').modal('hide');
$('#edit-user-modal').modal('dispose');
this.attrs.onSave(user); this.attrs.onSave(user);
@ -180,12 +144,19 @@ export default Component.extend(AuthProvider, DropdownMixin, {
} }
}, },
delete() { onShowDelete(id) {
this.closeDropdown(); this.set('deleteUser', this.users.findBy("id", id));
this.set('showDeleteDialog', true);
},
onDelete() {
this.set('showDeleteDialog', false);
this.set('selectedUsers', []); this.set('selectedUsers', []);
this.set('hasSelectedUsers', false); this.set('hasSelectedUsers', false);
this.attrs.onDelete(this.get('deleteUser.id')); this.attrs.onDelete(this.get('deleteUser.id'));
return true;
}, },
onBulkDelete() { onBulkDelete() {

View file

@ -10,7 +10,6 @@
// https://documize.com // https://documize.com
import { empty, and } from '@ember/object/computed'; import { empty, and } from '@ember/object/computed';
import Component from '@ember/component'; import Component from '@ember/component';
import { isEmpty } from '@ember/utils'; import { isEmpty } from '@ember/utils';
import { get, set } from '@ember/object'; import { get, set } from '@ember/object';

View file

@ -10,12 +10,10 @@
// https://documize.com // https://documize.com
import { set } from '@ember/object'; import { set } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import NotifierMixin from '../../../mixins/notifier';
export default Controller.extend(NotifierMixin, { export default Controller.extend({
userService: service('user'), userService: service('user'),
newUser: { firstname: "", lastname: "", email: "", active: true }, newUser: { firstname: "", lastname: "", email: "", active: true },
@ -26,7 +24,6 @@ export default Controller.extend(NotifierMixin, {
return this.get('userService') return this.get('userService')
.add(this.get('newUser')) .add(this.get('newUser'))
.then((user) => { .then((user) => {
this.showNotification('Added');
this.get('model').pushObject(user); this.get('model').pushObject(user);
}) })
.catch(function (error) { .catch(function (error) {
@ -38,8 +35,6 @@ export default Controller.extend(NotifierMixin, {
onDelete(userId) { onDelete(userId) {
let self = this; let self = this;
this.get('userService').remove(userId).then(function () { this.get('userService').remove(userId).then(function () {
self.showNotification('Deleted');
self.get('userService').getComplete().then(function (users) { self.get('userService').getComplete().then(function (users) {
self.set('model', users); self.set('model', users);
}); });
@ -49,7 +44,6 @@ export default Controller.extend(NotifierMixin, {
onSave(user) { onSave(user) {
let self = this; let self = this;
this.get('userService').save(user).then(function () { this.get('userService').save(user).then(function () {
self.showNotification('Saved');
self.get('userService').getComplete().then(function (users) { self.get('userService').getComplete().then(function (users) {
self.set('model', users); self.set('model', users);
@ -59,7 +53,6 @@ export default Controller.extend(NotifierMixin, {
onPassword(user, password) { onPassword(user, password) {
this.get('userService').updatePassword(user.id, password); this.get('userService').updatePassword(user.id, password);
this.showNotification('Password changed');
} }
} }
}); });

View file

@ -1,3 +1,12 @@
<div class="row">
<div class="col">
<div class="view-customize">
<h1 class="admin-heading">Users</h1>
<h2 class="sub-heading">Set basic information, passwords and permissions for {{model.length}} users</h2>
</div>
</div>
</div>
{{customize/user-settings add=(action 'add')}} {{customize/user-settings add=(action 'add')}}
{{customize/user-admin users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}} {{customize/user-admin users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}}

View file

@ -10,71 +10,18 @@
color: $color-gray; color: $color-gray;
} }
> .user-admin { .user-table {
margin: 30px 0; .name {
> .heading {
font-size: 1.2rem;
color: $color-off-black;
font-weight: bold;
margin: 0 0 20px 0;
}
> .basic-table {
background-color: $color-white;
border: none !important;
font-size: 1rem;
color: $color-off-black;
> thead {
> tr {
> th {
height: 40px;
font-weight: bold;
color: $color-gray;
}
> th:first-child {
font-weight: normal;
}
> th:last-child {
text-align: center;
}
}
}
> tbody {
> tr {
> td {
vertical-align: middle;
text-align: center;
> .selector {
> i {
color: $color-off-black;
}
}
> .name {
font-size: 1rem; font-size: 1rem;
color: $color-off-black; color: $color-off-black;
margin: 0 0 0 30px; margin: 0 0 0 30px;
} }
> .email { .email {
font-size: 0.9rem; font-size: 0.9rem;
color: $color-gray; color: $color-gray;
margin: 0 0 0 30px; margin: 0 0 0 30px;
} }
}
> td:first-child, td:last-child {
text-align: left;
}
}
}
}
.inactive-user .inactive-user
{ {
@ -95,10 +42,6 @@
} }
} }
.edit-user-dialog, .delete-user-dialog {
display: none;
}
> .space-list { > .space-list {
padding: 0; padding: 0;
margin: 0; margin: 0;

View file

@ -26,8 +26,10 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Anonymous Access</label> <label class="col-sm-2 col-form-label">Anonymous Access</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="checkbox" id="allowAnonymousAccess" checked={{model.general.allowAnonymousAccess}} /> <label class="form-check-label">
<small class="form-text text-muted">Content within "Everyone" will be made available to anonymous users</small> <input type="checkbox" class="form-check-input" id="allowAnonymousAccess" checked={{model.general.allowAnonymousAccess}} />
Make content marked as "Everyone" available to anonymous users
</label>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">

View file

@ -1,8 +1,8 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="view-customize"> <div class="view-customize">
<h1 class="admin-heading">SMTP Settings</h1> <h1 class="admin-heading">SMTP Server</h1>
<h2 class="sub-heading">Used for sending email notifications</h2> <h2 class="sub-heading">For sending email notifications</h2>
</div> </div>
</div> </div>
</div> </div>
@ -10,35 +10,35 @@
<div class="view-customize"> <div class="view-customize">
<form class="mt-5"> <form class="mt-5">
<div class="form-group row"> <div class="form-group row">
<label for="smtp-host" class="col-sm-2 col-form-label">SMTP Host</label> <label for="smtp-host" class="col-sm-2 col-form-label">Host</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{focus-input id="smtp-host" type="text" value=model.smtp.host class=(if SMTPHostEmptyError 'form-control is-invalid' 'form-control')}} {{focus-input id="smtp-host" type="text" value=model.smtp.host class=(if SMTPHostEmptyError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">e.g. my.host.com</small> <small class="form-text text-muted">e.g. my.host.com</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="smtp-port" class="col-sm-2 col-form-label">SMTP Port</label> <label for="smtp-port" class="col-sm-2 col-form-label">Port</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{input id="smtp-port" type="text" value=model.smtp.port class=(if SMTPPortEmptyError 'form-control is-invalid' 'form-control')}} {{input id="smtp-port" type="text" value=model.smtp.port class=(if SMTPPortEmptyError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">e.g. 587</small> <small class="form-text text-muted">e.g. 587</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="smtp-sender" class="col-sm-2 col-form-label">SMTP Sender</label> <label for="smtp-sender" class="col-sm-2 col-form-label">Sender</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{input id="smtp-sender" type="text" value=model.smtp.sender class=(if SMTPSenderEmptyError 'form-control is-invalid' 'form-control')}} {{input id="smtp-sender" type="text" value=model.smtp.sender class=(if SMTPSenderEmptyError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">e.g. user@some-domain.com</small> <small class="form-text text-muted">e.g. user@some-domain.com</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="smtp-userid" class="col-sm-2 col-form-label">SMTP Username</label> <label for="smtp-userid" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{input id="smtp-userid" type="text" value=model.smtp.userid class=(if SMTPUserIdEmptyError 'form-control is-invalid' 'form-control')}} {{input id="smtp-userid" type="text" value=model.smtp.userid class=(if SMTPUserIdEmptyError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">e.g. Login username for SMTP server</small> <small class="form-text text-muted">e.g. Login username for SMTP server</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="smtp-password" class="col-sm-2 col-form-label">SMTP Password</label> <label for="smtp-password" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{input id="smtp-password" type="password" value=model.smtp.password class=(if SMTPPasswordEmptyError 'form-control is-invalid' 'form-control')}} {{input id="smtp-password" type="password" value=model.smtp.password class=(if SMTPPasswordEmptyError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">e.g. Login password for SMTP server</small> <small class="form-text text-muted">e.g. Login password for SMTP server</small>

View file

@ -1,27 +1,16 @@
<div class="page-customize"> <div class="view-customize mb-5">
<div class="user-admin"> <h3>Users</h3>
<div class="form-header"> <table class="table table-hover table-responsive user-table">
<div class="title">User Management</div>
<div class="tip">Set basic information, passwords and permissions for {{users.length}} users</div>
</div>
<table class="basic-table">
<thead> <thead>
<tr> <tr>
<th class=""> <th>{{input type="text" class="form-control" placeholder="filter users" value=filter}}</th>
<div class="input-inline input-transparent">
{{focus-input type="text" placeholder="< type here to filter users >" value=filter}}
</div>
</th>
<th class="no-width">Add Space</th> <th class="no-width">Add Space</th>
<th class="no-width">View Users</th> <th class="no-width">View Users</th>
<th class="no-width">Admin</th> <th class="no-width">Admin</th>
<th class="no-width">Active</th> <th class="no-width">Active</th>
<th class="no-width"> <th class="no-width">
{{#if hasSelectedUsers}} {{#if hasSelectedUsers}}
<div class="round-button round-button-small button-red" id="bulk-delete-users"> <button id="bulk-delete-users" type="button" class="btn btn-danger btn-sm">Delete</button>
<i class="material-icons">delete</i>
</div>
{{#dropdown-dialog target="bulk-delete-users" position="bottom right" button="Delete" color="flat-red" onAction=(action 'onBulkDelete')}} {{#dropdown-dialog target="bulk-delete-users" position="bottom right" button="Delete" color="flat-red" onAction=(action 'onBulkDelete')}}
<p>Are you sure you want to delete selected users?</p> <p>Are you sure you want to delete selected users?</p>
{{/dropdown-dialog}} {{/dropdown-dialog}}
@ -30,10 +19,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{#each users key="id" as |user|}} {{#each filteredUsers key="id" as |user|}}
<tr> <tr>
<td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}"> <td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}">
<div class="selector pull-left"> <div class="d-inline-block align-top">
{{#if user.me}} {{#if user.me}}
<i class="material-icons color-gray">check_box_outline_blank</i> <i class="material-icons color-gray">check_box_outline_blank</i>
{{else if user.selected}} {{else if user.selected}}
@ -42,8 +31,10 @@
<i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box_outline_blank</i> <i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box_outline_blank</i>
{{/if}} {{/if}}
</div> </div>
<div class="name">{{ user.fullname }}</div> <div class="d-inline-block">
<div class="name d-inline-block">{{ user.fullname }}</div>
<div class="email">{{ user.email }}</div> <div class="email">{{ user.email }}</div>
</div>
</td> </td>
<td class="no-width text-center"> <td class="no-width text-center">
{{#if user.editor}} {{#if user.editor}}
@ -79,15 +70,15 @@
</td> </td>
<td class="no-width text-center"> <td class="no-width text-center">
{{#if user.me}} {{#if user.me}}
<div class="edit-button-{{user.id}} round-button-mono" title="Edit" {{action "edit" user.id}}> <div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" user.id}}>
<i class="material-icons">edit</i> <i class="material-icons">edit</i>
</div> </div>
{{else}} {{else}}
<div class="edit-button-{{user.id}} round-button-mono" title="Edit" {{action "edit" user.id}}> <div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" user.id}}>
<i class="material-icons">edit</i> <i class="material-icons">edit</i>
</div> </div>
<div class="button-gap"></div> <div class="button-icon-gap"></div>
<div class="delete-button-{{user.id}} round-button-mono" title="Delete" {{action "confirmDelete" user.id}}> <div class="delete-button-{{user.id}} button-icon-danger" title="Delete" {{action "onShowDelete" user.id}}>
<i class="material-icons">delete</i> <i class="material-icons">delete</i>
</div> </div>
{{/if}} {{/if}}
@ -98,74 +89,53 @@
</table> </table>
</div> </div>
<div class="dropdown-dialog edit-user-dialog"> <div id="edit-user-modal" class="modal" tabindex="-1" role="dialog">
<div class="content"> <div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">User {{editUser.firstname}} {{editUser.lastname}}</div>
<div class="modal-body">
<form> <form>
<div class="row"> <div class="form-group">
<div class="col-md-6"> <label for="edit-firstname">Firstname</label>
<div class="input-control"> {{input id="edit-firstname" class="form-control" type="text" value=editUser.firstname}}
<label>Firstname</label>
{{input id="edit-firstname" type="text" value=editUser.firstname}}
</div>
</div>
<div class="col-md-6">
<div class="input-control">
<label>Lastname</label>
{{input id="edit-lastname" type="text" value=editUser.lastname}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="input-control">
<label>Email</label>
{{input id="edit-email" type="text" value=editUser.email}}
</div> </div>
<div class="form-group">
<label for="edit-lastname">Lastname</label>
{{input id="edit-lastname" type="text" class="form-control" value=editUser.lastname}}
</div> </div>
<div class="form-group">
<label for="edit-email">Email</label>
{{input id="edit-email" type="text" class="form-control" value=editUser.email}}
</div> </div>
{{#if isAuthProviderDocumize}} {{#if isAuthProviderDocumize}}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="input-control"> <div class="form-group">
<label>Password</label> <label for="edit-password">Password</label>
<div class="tip">Optional new password</div> <div class="tip">Optional new password</div>
{{input id="edit-password" type="password" value=password.password}} {{input id="edit-password" type="password" class="form-control" value=password.password}}
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="input-control"> <div class="form-group">
<label>Confirm Password</label> <label for="edit-confirmPassword">Confirm Password</label>
<div class="tip">Confirm new password</div> <div class="tip">Confirm new password</div>
{{input id="edit-confirmPassword" type="password" value=password.confirmation}} {{input id="edit-confirmPassword" type="password" class="form-control" value=password.confirmation}}
</div> </div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
</form> </form>
</div> </div>
<div class="actions"> <div class="modal-footer">
<div class="flat-button" {{action 'cancel'}}> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
cancel <button type="button" class="btn btn-outline-success" onclick={{action 'onUpdate'}}>Save</button>
</div> </div>
<div class="flat-button flat-blue" {{action 'save'}}>
save
</div> </div>
</div> </div>
<div class="clearfix"></div>
</div> </div>
<div class="dropdown-dialog delete-user-dialog"> {{#ui/ui-dialog title="Delete User" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteDialog onAction=(action 'onDelete')}}
<div class="content"> <p>Are you sure you want to delete {{deleteUser.fullname}}?</p>
<p>Are you sure you want to delete user <span class="font-weight-bold">{{deleteUser.fullname}}?</span></p> {{/ui/ui-dialog}}
</div>
<div class="actions">
<div class="flat-button" {{action 'cancel'}}>
cancel
</div>
<div class="flat-button flat-red" {{action 'delete'}}>
delete
</div>
</div>
<div class="clearfix"></div>
</div>
</div>

View file

@ -1,25 +1,17 @@
{{#if isAuthProviderDocumize}} {{#if isAuthProviderDocumize}}
<div class="page-customize"> <div class="view-customize mt-5 mb-5">
<div class="add-user"> <h3>Add User</h3>
<form> <form class="form-inline">
<div class="form-header"> <label class="sr-only" for="newUserFirstname">Firstname</label>
<div class="title">New User</div> {{focus-input id="newUserFirstname" type="text" value=newUser.firstname placeholder="Firstname" class=(if hasFirstnameEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
<div class="tip">Newly added users receive an invitation email with a random password</div>
</div> <label class="sr-only" for="newUserLastname">Lastname</label>
<div class="input-control"> {{input id="newUserLastname" type="text" value=newUser.lastname placeholder="Lastname" class=(if hasLastnameEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
<label>Firstname</label>
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname class=(if hasFirstnameEmptyError 'error')}} <label class="sr-only" for="newEmail">Email</label>
</div> {{input id="newEmail" type="email" value=newUser.email placeholder="Email" class=(if hasEmailEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
<div class="input-control">
<label>Lastname</label> <button type="submit" class="btn btn-success" {{action 'add'}}>Add</button>
{{input id="newUserLastname" type="text" value=newUser.lastname class=(if hasLastnameEmptyError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="newUserEmail" type="text" value=newUser.email class=(if hasEmailEmptyError 'error')}}
</div>
<div class="regular-button button-blue" {{ action 'add' }}>Add</div>
</form> </form>
</div> </div>
</div>
{{/if}} {{/if}}