DEV: Convert `penalize-user` modal to component-based API (#22960)
<img width="681" alt="Screenshot 2023-08-03 at 12 55 08 PM" src="https://github.com/discourse/discourse/assets/50783505/79cc045a-523d-45a2-8c33-04b556331358"> <img width="763" alt="Screenshot 2023-08-03 at 12 55 05 PM" src="https://github.com/discourse/discourse/assets/50783505/7196a97f-e4f4-4870-b8ac-77255d604c27"> <img width="711" alt="Screenshot 2023-08-03 at 12 55 11 PM" src="https://github.com/discourse/discourse/assets/50783505/a916a85d-8bdb-41fb-8210-1e0c06cf7cf1">
This commit is contained in:
parent
ba46b34581
commit
a5542eeab0
|
@ -1,12 +1,13 @@
|
|||
<DModalBody @title={{this.modalTitle}}>
|
||||
<ConditionalLoadingSpinner @condition={{this.loadingUser}}>
|
||||
{{#if this.errorMessage}}
|
||||
<div class="alert alert-error">{{this.errorMessage}}</div>
|
||||
{{/if}}
|
||||
|
||||
<DModal
|
||||
class="{{@model.penaltyType}}-user-modal"
|
||||
@title={{i18n this.modalTitle}}
|
||||
@closeModal={{this.warnBeforeClosing}}
|
||||
@flash={{this.flash}}
|
||||
>
|
||||
<:body>
|
||||
{{#if this.canPenalize}}
|
||||
<div class="penalty-duration-controls">
|
||||
{{#if (eq this.penaltyType "suspend")}}
|
||||
{{#if (eq @model.penaltyType "suspend")}}
|
||||
<FutureDateInput
|
||||
@class="suspend-until"
|
||||
@label="admin.user.suspend_duration"
|
||||
|
@ -14,7 +15,7 @@
|
|||
@input={{this.penalizeUntil}}
|
||||
@onChangeInput={{action (mut this.penalizeUntil)}}
|
||||
/>
|
||||
{{else if (eq this.penaltyType "silence")}}
|
||||
{{else if (eq @model.penaltyType "silence")}}
|
||||
<FutureDateInput
|
||||
@class="silence-until"
|
||||
@label="admin.user.silence_duration"
|
||||
|
@ -24,8 +25,7 @@
|
|||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if (eq this.penaltyType "suspend")}}
|
||||
{{#if (eq @model.penaltyType "suspend")}}
|
||||
<div class="penalty-reason-visibility">
|
||||
{{#if this.siteSettings.hide_suspension_reasons}}
|
||||
{{html-safe (i18n "admin.user.suspend_reason_hidden_label")}}
|
||||
|
@ -34,48 +34,46 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<AdminPenaltyReason
|
||||
@penaltyType={{this.penaltyType}}
|
||||
@penaltyType={{@model.penaltyType}}
|
||||
@reason={{this.reason}}
|
||||
@message={{this.message}}
|
||||
/>
|
||||
|
||||
{{#if this.postId}}
|
||||
{{#if @model.postId}}
|
||||
<AdminPenaltyPostAction
|
||||
@postId={{this.postId}}
|
||||
@postId={{@model.postId}}
|
||||
@postAction={{this.postAction}}
|
||||
@postEdit={{this.postEdit}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.user.similar_users}}
|
||||
<AdminPenaltySimilarUsers
|
||||
@penaltyType={{this.penaltyType}}
|
||||
@penaltyType={{@model.penaltyType}}
|
||||
@user={{this.user}}
|
||||
@selectedUserIds={{this.otherUserIds}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
{{#if (eq this.penaltyType "suspend")}}
|
||||
{{#if (eq @model.penaltyType "suspend")}}
|
||||
<div class="cant-suspend">{{i18n "admin.user.cant_suspend"}}</div>
|
||||
{{else if (eq this.penaltyType "silence")}}
|
||||
{{else if (eq @model.penaltyType "silence")}}
|
||||
<div class="cant-silence">{{i18n "admin.user.cant_silence"}}</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div class="penalty-history">{{html-safe this.penaltyHistory}}</div>
|
||||
</ConditionalLoadingSpinner>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-danger perform-penalize"
|
||||
@action={{this.penalizeUser}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@icon="ban"
|
||||
@label={{this.buttonLabel}}
|
||||
/>
|
||||
<DModalCancel @close={{route-action "closeModal"}} />
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} @size="small" />
|
||||
</div>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
class="btn-danger perform-penalize"
|
||||
@action={{this.penalizeUser}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@icon="ban"
|
||||
@label={{this.buttonLabel}}
|
||||
/>
|
||||
<DButton
|
||||
class="btn-flat d-modal-cancel"
|
||||
@action={{this.warnBeforeClosing}}
|
||||
@label="cancel"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,118 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class PenalizeUser extends Component {
|
||||
@service dialog;
|
||||
@service siteSettings;
|
||||
|
||||
@tracked penalizeUntil = this.args.model.user.next_penalty;
|
||||
@tracked confirmClose = false;
|
||||
@tracked otherUserIds = [];
|
||||
@tracked postAction = "delete";
|
||||
@tracked postEdit = this.args.model.postEdit;
|
||||
@tracked flash;
|
||||
@tracked reason;
|
||||
@tracked message;
|
||||
|
||||
get modalTitle() {
|
||||
if (this.args.model.penaltyType === "suspend") {
|
||||
return "admin.user.suspend_modal_title";
|
||||
} else if (this.args.model.penaltyType === "silence") {
|
||||
return "admin.user.silence_modal_title";
|
||||
}
|
||||
}
|
||||
|
||||
get buttonLabel() {
|
||||
if (this.args.model.penaltyType === "suspend") {
|
||||
return "admin.user.suspend";
|
||||
} else if (this.args.model.penaltyType === "silence") {
|
||||
return "admin.user.silence";
|
||||
}
|
||||
}
|
||||
|
||||
get penaltyHistory() {
|
||||
return I18n.messageFormat("admin.user.penalty_history_MF", {
|
||||
SUSPENDED: this.args.model.user.penalty_counts?.suspended,
|
||||
SILENCED: this.args.model.user.penalty_counts?.silenced,
|
||||
});
|
||||
}
|
||||
|
||||
get canPenalize() {
|
||||
if (this.args.model.penaltyType === "suspend") {
|
||||
return this.args.model.user.canSuspend;
|
||||
} else if (this.args.model.penaltyType === "silence") {
|
||||
return this.args.model.user.canSilence;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get submitDisabled() {
|
||||
return (
|
||||
this.penalizing ||
|
||||
isEmpty(this.penalizeUntil) ||
|
||||
!this.reason ||
|
||||
this.reason.length < 1
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
async penalizeUser() {
|
||||
if (this.submitDisabled) {
|
||||
return;
|
||||
}
|
||||
this.penalizing = true;
|
||||
this.confirmClose = true;
|
||||
if (this.before) {
|
||||
this.before();
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
const opts = {
|
||||
reason: this.reason,
|
||||
message: this.message,
|
||||
post_id: this.args.model.postId,
|
||||
post_action: this.postAction,
|
||||
post_edit: this.postEdit,
|
||||
other_user_ids: this.otherUserIds,
|
||||
};
|
||||
|
||||
if (this.args.model.penaltyType === "suspend") {
|
||||
opts.suspend_until = this.penalizeUntil;
|
||||
result = await this.args.model.user.suspend(opts);
|
||||
} else if (this.args.model.penaltyType === "silence") {
|
||||
opts.silenced_till = this.penalizeUntil;
|
||||
result = await this.args.model.user.silence(opts);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Unknown penalty type:", this.args.model.penaltyType);
|
||||
}
|
||||
this.args.closeModal();
|
||||
if (this.successCallback) {
|
||||
await this.successCallback(result);
|
||||
}
|
||||
} catch {
|
||||
this.flash = extractError(result);
|
||||
} finally {
|
||||
this.penalizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
warnBeforeClosing() {
|
||||
if (!this.confirmClose && (this.reason?.length || this.message?.length)) {
|
||||
this.dialog.confirm({
|
||||
message: I18n.t("admin.user.confirm_cancel_penalty"),
|
||||
didConfirm: () => this.args.closeModal(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
this.args.closeModal();
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
import { inject as service } from "@ember/service";
|
||||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class AdminPenalizeUserController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service dialog;
|
||||
|
||||
loadingUser = false;
|
||||
errorMessage = null;
|
||||
penaltyType = null;
|
||||
penalizeUntil = null;
|
||||
reason = null;
|
||||
message = null;
|
||||
postId = null;
|
||||
postAction = null;
|
||||
postEdit = null;
|
||||
user = null;
|
||||
otherUserIds = null;
|
||||
loading = false;
|
||||
confirmClose = false;
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
loadingUser: true,
|
||||
errorMessage: null,
|
||||
penaltyType: null,
|
||||
penalizeUntil: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
postId: null,
|
||||
postAction: "delete",
|
||||
postEdit: null,
|
||||
user: null,
|
||||
otherUserIds: [],
|
||||
loading: false,
|
||||
errorMessage: null,
|
||||
reason: null,
|
||||
message: null,
|
||||
confirmClose: false,
|
||||
});
|
||||
}
|
||||
|
||||
finishedSetup() {
|
||||
this.set("penalizeUntil", this.user?.next_penalty);
|
||||
}
|
||||
|
||||
beforeClose() {
|
||||
if (this.confirmClose) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
(this.reason && this.reason.length > 1) ||
|
||||
(this.message && this.message.length > 1)
|
||||
) {
|
||||
this.send("hideModal");
|
||||
this.dialog.confirm({
|
||||
message: I18n.t("admin.user.confirm_cancel_penalty"),
|
||||
didConfirm: () => {
|
||||
next(() => {
|
||||
this.set("confirmClose", true);
|
||||
this.send("closeModal");
|
||||
});
|
||||
},
|
||||
didCancel: () => this.send("reopenModal"),
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed("penaltyType")
|
||||
modalTitle(penaltyType) {
|
||||
if (penaltyType === "suspend") {
|
||||
return "admin.user.suspend_modal_title";
|
||||
} else if (penaltyType === "silence") {
|
||||
return "admin.user.silence_modal_title";
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed("penaltyType")
|
||||
buttonLabel(penaltyType) {
|
||||
if (penaltyType === "suspend") {
|
||||
return "admin.user.suspend";
|
||||
} else if (penaltyType === "silence") {
|
||||
return "admin.user.silence";
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"user.penalty_counts.suspended",
|
||||
"user.penalty_counts.silenced"
|
||||
)
|
||||
penaltyHistory(suspendedCount, silencedCount) {
|
||||
return I18n.messageFormat("admin.user.penalty_history_MF", {
|
||||
SUSPENDED: suspendedCount,
|
||||
SILENCED: silencedCount,
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed("penaltyType", "user.canSuspend", "user.canSilence")
|
||||
canPenalize(penaltyType, canSuspend, canSilence) {
|
||||
if (penaltyType === "suspend") {
|
||||
return canSuspend;
|
||||
} else if (penaltyType === "silence") {
|
||||
return canSilence;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@discourseComputed("penalizing", "penalizeUntil", "reason")
|
||||
submitDisabled(penalizing, penalizeUntil, reason) {
|
||||
return penalizing || isEmpty(penalizeUntil) || !reason || reason.length < 1;
|
||||
}
|
||||
|
||||
@action
|
||||
async penalizeUser() {
|
||||
if (this.submitDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("penalizing", true);
|
||||
this.set("confirmClose", true);
|
||||
|
||||
if (this.before) {
|
||||
this.before();
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
const opts = {
|
||||
reason: this.reason,
|
||||
message: this.message,
|
||||
post_id: this.postId,
|
||||
post_action: this.postAction,
|
||||
post_edit: this.postEdit,
|
||||
other_user_ids: this.otherUserIds,
|
||||
};
|
||||
|
||||
if (this.penaltyType === "suspend") {
|
||||
opts.suspend_until = this.penalizeUntil;
|
||||
result = await this.user.suspend(opts);
|
||||
} else if (this.penaltyType === "silence") {
|
||||
opts.silenced_till = this.penalizeUntil;
|
||||
result = await this.user.silence(opts);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Unknown penalty type:", this.penaltyType);
|
||||
}
|
||||
|
||||
this.send("closeModal");
|
||||
if (this.successCallback) {
|
||||
await this.successCallback(result);
|
||||
}
|
||||
} catch {
|
||||
this.set("errorMessage", extractError(result));
|
||||
} finally {
|
||||
this.set("penalizing", false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,14 +4,16 @@ import { Promise } from "rsvp";
|
|||
import Service, { inject as service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { action } from "@ember/object";
|
||||
import PenalizeUserModal from "admin/components/modal/penalize-user";
|
||||
|
||||
// A service that can act as a bridge between the front end Discourse application
|
||||
// and the admin application. Use this if you need front end code to access admin
|
||||
// modules. Inject it optionally, and if it exists go to town!
|
||||
export default class AdminToolsService extends Service {
|
||||
@service dialog;
|
||||
@service modal;
|
||||
|
||||
showActionLogs(target, filters) {
|
||||
const controller = getOwner(target).lookup(
|
||||
|
@ -39,42 +41,30 @@ export default class AdminToolsService extends Service {
|
|||
};
|
||||
}
|
||||
|
||||
_showControlModal(type, user, opts) {
|
||||
@action
|
||||
async showControlModal(type, user, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const controller = showModal(`admin-penalize-user`, {
|
||||
admin: true,
|
||||
modalClass: `${type}-user-modal`,
|
||||
});
|
||||
|
||||
controller.setProperties({
|
||||
penaltyType: type,
|
||||
postId: opts.postId,
|
||||
postEdit: opts.postEdit,
|
||||
});
|
||||
|
||||
return (
|
||||
user.adminUserView
|
||||
? Promise.resolve(user)
|
||||
: AdminUser.find(user.get("id"))
|
||||
).then((loadedUser) => {
|
||||
controller.setProperties({
|
||||
const loadedUser = user.adminUserView
|
||||
? user
|
||||
: await AdminUser.find(user.get("id"));
|
||||
this.modal.show(PenalizeUserModal, {
|
||||
model: {
|
||||
penaltyType: type,
|
||||
postId: opts.postId,
|
||||
postEdit: opts.postEdit,
|
||||
user: loadedUser,
|
||||
loadingUser: false,
|
||||
before: opts.before,
|
||||
successCallback: opts.successCallback,
|
||||
});
|
||||
|
||||
controller.finishedSetup();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
showSilenceModal(user, opts) {
|
||||
this._showControlModal("silence", user, opts);
|
||||
this.showControlModal("silence", user, opts);
|
||||
}
|
||||
|
||||
showSuspendModal(user, opts) {
|
||||
this._showControlModal("suspend", user, opts);
|
||||
this.showControlModal("suspend", user, opts);
|
||||
}
|
||||
|
||||
_deleteSpammer(adminUser) {
|
||||
|
|
|
@ -45,7 +45,6 @@ const KNOWN_LEGACY_MODALS = [
|
|||
"tag-upload",
|
||||
"topic-summary",
|
||||
"user-status",
|
||||
"admin-penalize-user",
|
||||
"admin-reseed",
|
||||
"admin-theme-item",
|
||||
"admin-color-scheme-select-base",
|
||||
|
|
|
@ -65,9 +65,9 @@ acceptance("Admin - Suspend User", function (needs) {
|
|||
|
||||
await click(".d-modal-cancel");
|
||||
assert.strictEqual(count(".dialog-body:visible"), 1);
|
||||
assert.ok(!exists(".suspend-user-modal:visible"));
|
||||
|
||||
await click(".dialog-footer .btn-primary");
|
||||
assert.ok(!exists(".suspend-user-modal:visible"));
|
||||
assert.ok(!exists(".dialog-body:visible"));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue