Skip to content

Commit

Permalink
Add ability to create new user from Users List page
Browse files Browse the repository at this point in the history
  • Loading branch information
davwheat committed Sep 10, 2021
1 parent eb6a6e8 commit d43b72f
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 14 deletions.
185 changes: 185 additions & 0 deletions js/src/admin/components/CreateUserModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import app from '../../admin/app';
import type Mithril from 'mithril';
import type { ComponentAttrs } from '../../common/Component';
import Button from '../../common/components/Button';
import Modal from '../../common/components/Modal';
import Switch from '../../common/components/Switch';
import ItemList from '../../common/utils/ItemList';
import type User from '../../common/models/User';
import EditUserModal from '../../common/components/EditUserModal';

interface ICreateUserModalState {
username: string;
email: string;
isEmailConfirmed: boolean;
password: string;
showEditModalAfterClose: boolean;
}

/**
* A Modal that allows admins to create a new user.
*/
export default class CreateUserModal extends Modal {
state: ICreateUserModalState = {
username: '',
email: '',
isEmailConfirmed: false,
password: '',
showEditModalAfterClose: true,
};

oninit(vnode: Mithril.Vnode<ComponentAttrs, this>) {
super.oninit(vnode);
}

className() {
return 'CreateUserModal';
}

title() {
return app.translator.trans('core.admin.create_user.title');
}

content() {
const fields = this.fields().toArray();

console.log(this.state);

return <div className="Modal-body">{fields}</div>;
}

fields() {
const items = new ItemList();

items.add(
'username',
<div className="Form-group">
<label>
{app.translator.trans('core.admin.create_user.username.label')}
<input className="FormControl" {...this.twoWayLinkAttrs('username')} />
</label>
</div>,
100
);

items.add(
'password',
<div className="Form-group">
<label>
{app.translator.trans('core.admin.create_user.password.label')}
<input autocomplete="new-password" type="password" className="FormControl" {...this.twoWayLinkAttrs('password')} />
</label>
</div>,
80
);

items.add(
'email',
[
<div className="Form-group">
<label>
{app.translator.trans('core.admin.create_user.email.label')}
<input type="email" className="FormControl" {...this.twoWayLinkAttrs('email')} />
</label>
</div>,
<div className="Form-group">
<Switch loading={this.loading} state={this.state.isEmailConfirmed} onchange={(val: boolean) => (this.state.isEmailConfirmed = val)}>
{app.translator.trans('core.admin.create_user.email.confirmed')}
</Switch>
</div>,
],
60
);

items.add(
'openEditAfterClose',
[
<div className="Form-group">
<label>
<input
type="checkbox"
checked={this.state.showEditModalAfterClose}
onchange={(e: InputEvent) => (this.state.showEditModalAfterClose = (e!.currentTarget as HTMLInputElement).checked)}
/>{' '}
{app.translator.trans('core.admin.create_user.launch_edit_after_close')}
</label>
</div>,
],
0
);

items.add(
'submit',
<div className="Form-group">
<Button className="Button Button--primary" type="submit" loading={this.loading}>
{app.translator.trans('core.admin.create_user.submit_button')}
</Button>
</div>,
-10
);

return items;
}

private twoWayLinkAttrs(key: keyof typeof this.state, valueName: string = 'value', eventName: string = 'oninput') {
return {
[valueName]: this.state[key],
[eventName]: (e: Event) => {
(this.state[key] as ICreateUserModalState[keyof ICreateUserModalState]) = (e!.currentTarget as HTMLInputElement).value;
},
};
}

isDataValid(): boolean {
const { username } = this.state;

if (!username) return false;

return true;
}

submitData(): { data: { attributes: Omit<ICreateUserModalState, 'showEditModalAfterClose'> } } {
const { showEditModalAfterClose, ...data } = this.state;

return { data: { attributes: data } };
}

onsubmit(e: SubmitEvent) {
e.preventDefault();

this.loading = true;

const body = this.submitData();

app
.request({
url: `${app.forum.attribute('apiUrl')}/users`,
method: 'POST',
body,
errorHandler: this.onerror.bind(this),
})
.then((response) => {
this.hide();
console.log(response);

const user: User = app.store.pushPayload(response);

// Add the missing groups relationship we can't include from the CreateUserController
user.pushData({
relationships: {
groups: {
data: [],
},
},
});

if (this.state.showEditModalAfterClose) {
app.modal.show(EditUserModal, { user });
}
})
.catch((e) => {
this.loaded.call(this);
this.onerror(e);
});
}
}
15 changes: 15 additions & 0 deletions js/src/admin/components/UserListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import extractText from '../../common/utils/extractText';

import AdminPage from './AdminPage';
import Checkbox from '../../common/components/Checkbox';
import CreateUserModal from './CreateUserModal';

type ColumnData = {
/**
Expand Down Expand Up @@ -190,6 +191,20 @@ export default class UserListPage extends AdminPage {
900
);

items.add(
'createUser',
<Button
icon="fas fa-user-plus"
class="Button"
onclick={() => {
app.modal.show(CreateUserModal);
}}
>
{app.translator.trans('core.admin.users.info_header.create_user.button')}
</Button>,
800
);

return items;
}

Expand Down
1 change: 1 addition & 0 deletions less/admin.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
@import "admin/MailPage";
@import "admin/NoJs";
@import "admin/UsersListPage.less";
@import "admin/CreateUserModal.less";
6 changes: 6 additions & 0 deletions less/admin/CreateUserModal.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.CreateUserModal {
label input {
margin-top: 2px;
font-weight: normal;
}
}
43 changes: 29 additions & 14 deletions locale/core.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
core:

##
# UNIQUE KEYS - The following keys are used in only one location each.
##

# Translations in this namespace are used by the admin interface.
admin:

# These translations are used in the Appearance page.
appearance:
colored_header_label: Colored Header
Expand Down Expand Up @@ -49,6 +47,23 @@ core:
welcome_banner_heading: Welcome Banner
welcome_banner_text: Configure the text that displays in the banner on the All Discussions page. Use this to welcome guests to your forum.

# These translations are used in the Create User modal.
create_user:
title: Create new user
submit_button: => core.ref.save_changes

email:
label: => core.ref.email
confirmed: Email confirmed?

launch_edit_after_close: Open edit user modal after creating this user?

password:
label: => core.ref.password

username:
label: => core.ref.username

# These translations are used in the Dashboard page.
dashboard:
clear_cache_button: Clear Cache
Expand Down Expand Up @@ -262,6 +277,9 @@ core:
button: Copy link to page {pageNumber}
copied: => core.ref.copied

create_user:
button: Create new user

pagination:
back_button: Previous page
first_button: First page
Expand All @@ -276,7 +294,6 @@ core:

# Translations in this namespace are used by the forum user interface.
forum:

# These translations are used in the Change Email modal dialog.
change_email:
confirm_password_placeholder: => core.ref.confirm_password
Expand Down Expand Up @@ -507,7 +524,6 @@ core:

# Translations in this namespace are used by the forum and admin interfaces.
lib:

# These translations are displayed as tooltips for discussion badges.
badge:
hidden_tooltip: Hidden
Expand Down Expand Up @@ -628,7 +644,6 @@ core:

# Translations in this namespace are used in emails sent by the forum.
email:

# These translations are used in emails sent when users register new accounts.
activate_account:
subject: Activate Your New Account
Expand Down Expand Up @@ -689,7 +704,7 @@ core:
all_discussions: All Discussions
change_email: Change Email
change_password: Change Password
color: Color # Referenced by flarum-tags.yml
color: Color # Referenced by flarum-tags.yml
confirm_password: Confirm Password
confirm_email: Confirm Email
confirmation_email_sent: "We've sent a confirmation email to {email}. If it doesn't arrive soon, check your spam folder."
Expand All @@ -700,7 +715,7 @@ core:
custom_header_title: Edit Custom Header
delete: Delete
delete_forever: Delete Forever
discussions: Discussions # Referenced by flarum-statistics.yml
discussions: Discussions # Referenced by flarum-statistics.yml
edit: Edit
edit_user: Edit User
email: Email
Expand All @@ -714,25 +729,25 @@ core:
mark_all_as_read: Mark All as Read
next_page: Next Page
notifications: Notifications
okay: OK # Referenced by flarum-tags.yml
okay: OK # Referenced by flarum-tags.yml
password: Password
posts: Posts # Referenced by flarum-statistics.yml
posts: Posts # Referenced by flarum-statistics.yml
previous_page: Previous Page
remove: Remove
rename: Rename
reply: Reply # Referenced by flarum-mentions.yml
reply: Reply # Referenced by flarum-mentions.yml
reset_your_password: Reset Your Password
restore: Restore
save_changes: Save Changes # Referenced by flarum-suspend.yml, flarum-tags.yml
save_changes: Save Changes # Referenced by flarum-suspend.yml, flarum-tags.yml
settings: Settings
sign_up: Sign Up
some_others: "{count, plural, one {# other} other {# others}}" # Referenced by flarum-likes.yml, flarum-mentions.yml
some_others: "{count, plural, one {# other} other {# others}}" # Referenced by flarum-likes.yml, flarum-mentions.yml
start_a_discussion: Start a Discussion
username: Username
users: Users # Referenced by flarum-statistics.yml
users: Users # Referenced by flarum-statistics.yml
view: View
write_a_reply: Write a Reply...
you: You # Referenced by flarum-likes.yml, flarum-mentions.yml
you: You # Referenced by flarum-likes.yml, flarum-mentions.yml

##
# GROUP NAMES - These keys are translated at the back end.
Expand Down

0 comments on commit d43b72f

Please sign in to comment.