account-settings: Disable deactivate account button when only owner.

Disables the deactivate account button in the user's account and
privacy settings tab if they are the only active organization owner.

Adds a tooltip when hovering on the deactivated button to let the
user know why the button is disabled.

The backend already returns an error for self account deactivation
requests if the user is the only organization owner.
This commit is contained in:
Lauryn Menard 2022-12-20 16:52:25 +01:00 committed by Tim Abbott
parent 7abf476443
commit 1a3b0edf4b
9 changed files with 83 additions and 3 deletions

View File

@ -642,6 +642,7 @@ run_test("realm_domains", ({override}) => {
}); });
run_test("realm_user", ({override}) => { run_test("realm_user", ({override}) => {
override(settings_account, "maybe_update_deactivate_account_button", noop);
let event = event_fixtures.realm_user__add; let event = event_fixtures.realm_user__add;
dispatch({...event}); dispatch({...event});
const added_person = people.get_by_user_id(event.person.user_id); const added_person = people.get_by_user_id(event.person.user_id);

View File

@ -590,6 +590,20 @@ test_people("set_custom_profile_field_data", () => {
assert.equal(person.profile_data[field.id].rendered_value, "<p>Field value</p>"); assert.equal(person.profile_data[field.id].rendered_value, "<p>Field value</p>");
}); });
test_people("is_current_user_only_owner", () => {
const person = people.get_by_email(me.email);
person.is_owner = false;
page_params.is_owner = false;
assert.ok(!people.is_current_user_only_owner());
person.is_owner = true;
page_params.is_owner = true;
assert.ok(people.is_current_user_only_owner());
people.add_active_user(realm_owner);
assert.ok(!people.is_current_user_only_owner());
});
test_people("recipient_counts", () => { test_people("recipient_counts", () => {
const user_id = 99; const user_id = 99;
assert.equal(people.get_recipient_count({user_id}), 0); assert.equal(people.get_recipient_count({user_id}), 0);

View File

@ -825,6 +825,23 @@ export function is_active_user_for_popover(user_id) {
return false; return false;
} }
export function is_current_user_only_owner() {
if (!page_params.is_owner || page_params.is_bot) {
return false;
}
let active_owners = 0;
for (const person of active_user_dict.values()) {
if (person.is_owner && !person.is_bot) {
active_owners += 1;
}
if (active_owners > 1) {
return false;
}
}
return true;
}
export function filter_all_persons(pred) { export function filter_all_persons(pred) {
const ret = []; const ret = [];
for (const person of people_by_user_id_dict.values()) { for (const person of people_by_user_id_dict.values()) {

View File

@ -429,15 +429,18 @@ export function dispatch_normal_event(event) {
switch (event.op) { switch (event.op) {
case "add": case "add":
people.add_active_user(event.person); people.add_active_user(event.person);
settings_account.maybe_update_deactivate_account_button();
break; break;
case "remove": case "remove":
people.deactivate(event.person); people.deactivate(event.person);
stream_events.remove_deactivated_user_from_all_streams(event.person.user_id); stream_events.remove_deactivated_user_from_all_streams(event.person.user_id);
settings_users.update_view_on_deactivate(event.person.user_id); settings_users.update_view_on_deactivate(event.person.user_id);
buddy_list.maybe_remove_key({key: event.person.user_id}); buddy_list.maybe_remove_key({key: event.person.user_id});
settings_account.maybe_update_deactivate_account_button();
break; break;
case "update": case "update":
user_events.update_person(event.person); user_events.update_person(event.person);
settings_account.maybe_update_deactivate_account_button();
break; break;
default: default:
blueslip.error("Unexpected event type realm_user/" + event.op); blueslip.error("Unexpected event type realm_user/" + event.op);

View File

@ -106,6 +106,7 @@ export function build_page() {
send_read_receipts_tooltip: $t({ send_read_receipts_tooltip: $t({
defaultMessage: "Read receipts are currently disabled in this organization.", defaultMessage: "Read receipts are currently disabled in this organization.",
}), }),
user_is_only_organization_owner: people.is_current_user_only_owner(),
}); });
$(".settings-box").html(rendered_settings_tab); $(".settings-box").html(rendered_settings_tab);

View File

@ -135,6 +135,23 @@ export function update_account_settings_display() {
update_avatar_change_display(); update_avatar_change_display();
} }
export function maybe_update_deactivate_account_button() {
if (!page_params.is_owner) {
return;
}
const $deactivate_account_container = $("#deactivate_account_container");
if ($deactivate_account_container) {
if (people.is_current_user_only_owner()) {
$("#user_deactivate_account_button").prop("disabled", true);
$deactivate_account_container.addClass("only_organization_owner_tooltip");
} else {
$("#user_deactivate_account_button").prop("disabled", false);
$deactivate_account_container.removeClass("only_organization_owner_tooltip");
}
}
}
export function update_send_read_receipts_tooltip() { export function update_send_read_receipts_tooltip() {
if (page_params.realm_enable_read_receipts) { if (page_params.realm_enable_read_receipts) {
$("#send_read_receipts_label .settings-info-icon").hide(); $("#send_read_receipts_label .settings-info-icon").hide();

View File

@ -407,6 +407,18 @@ export function initialize() {
}, },
}); });
delegate("body", {
target: ["#deactivate_account_container.only_organization_owner_tooltip"],
content: $t({
defaultMessage:
"Because you are the only organization owner, you cannot deactivate your account.",
}),
appendTo: () => document.body,
onHidden(instance) {
instance.destroy();
},
});
delegate("body", { delegate("body", {
target: "#pm_tooltip_container", target: "#pm_tooltip_container",
onShow(instance) { onShow(instance) {

View File

@ -183,6 +183,18 @@ h3,
} }
} }
#deactivate_account_container {
&.only_organization_owner_tooltip {
cursor: not-allowed;
}
}
#user_deactivate_account_button {
&:disabled {
pointer-events: none;
}
}
.admin-realm-description { .admin-realm-description {
height: 16em; height: 16em;
width: 100%; width: 100%;

View File

@ -36,9 +36,12 @@
</form> </form>
<div class="input-group"> <div class="input-group">
<button type="submit" class="button rounded btn-danger" id="user_deactivate_account_button"> <div id="deactivate_account_container" class="inline-block {{#if user_is_only_organization_owner}}only_organization_owner_tooltip{{/if}}">
{{t 'Deactivate account' }} <button type="submit" class="button rounded btn-danger" id="user_deactivate_account_button"
</button> {{#if user_is_only_organization_owner}}disabled="disabled"{{/if}}>
{{t 'Deactivate account' }}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>