Add admin delete-account flow with yes/no confirmation

This commit is contained in:
Jage9
2026-02-28 20:06:43 -05:00
parent b0fa040d33
commit 906c320e51
12 changed files with 239 additions and 6 deletions

View File

@@ -210,7 +210,8 @@ type AdminUserSummary = {
type AdminPendingUserMutation =
| { action: 'set_role'; username: string; role: string }
| { action: 'ban'; username: string }
| { action: 'unban'; username: string };
| { action: 'unban'; username: string }
| { action: 'delete_account'; username: string };
type ItemManagementAction = 'delete' | 'transfer';
@@ -332,10 +333,11 @@ let adminRolePermissionIndex = 0;
let adminRoleDeleteReplacementIndex = 0;
let adminUsers: AdminUserSummary[] = [];
let adminUserIndex = 0;
let adminPendingUserAction: 'set_role' | 'ban' | 'unban' | null = null;
let adminPendingUserAction: 'set_role' | 'ban' | 'unban' | 'delete_account' | null = null;
let adminSelectedRoleName = '';
let adminSelectedUsername = '';
let adminPendingUserMutation: AdminPendingUserMutation | null = null;
let adminDeleteConfirmIndex = 0;
let itemManagementSelectedItemId: string | null = null;
let itemManagementOptions: ItemManagementOption[] = [];
let itemManagementOptionIndex = 0;
@@ -1830,6 +1832,16 @@ function handleAdminActionResult(message: Extract<IncomingMessage, { type: 'admi
adminPendingUserAction = null;
}
}
} else if (adminPendingUserMutation.action === 'delete_account') {
adminUsers = adminUsers.filter((entry) => entry.username !== adminPendingUserMutation.username);
if (state.mode === 'adminUserList' && adminPendingUserAction === 'delete_account') {
if (adminUsers.length > 0) {
adminUserIndex = Math.max(0, Math.min(adminUserIndex, adminUsers.length - 1));
} else {
state.mode = 'adminMenu';
adminPendingUserAction = null;
}
}
}
adminPendingUserMutation = null;
}
@@ -3029,6 +3041,12 @@ function handleAdminMenuModeInput(code: string, key: string): void {
adminPendingUserAction = 'unban';
signaling.send({ type: 'admin_users_list', action: 'unban' });
updateStatus('Loading users...');
return;
}
if (selected.id === 'delete_account') {
adminPendingUserAction = 'delete_account';
signaling.send({ type: 'admin_users_list', action: 'delete_account' });
updateStatus('Loading users...');
}
return;
}
@@ -3217,6 +3235,13 @@ function handleAdminUserListModeInput(code: string, key: string): void {
adminPendingUserAction = 'unban';
return;
}
if (adminPendingUserAction === 'delete_account') {
adminDeleteConfirmIndex = 0;
state.mode = 'adminUserDeleteConfirm';
updateStatus(`Delete account ${selected.username}? ${YES_NO_OPTIONS[adminDeleteConfirmIndex].label}.`);
audio.sfxUiBlip();
return;
}
return;
}
if (control.type === 'cancel') {
@@ -3254,6 +3279,48 @@ function handleAdminUserRoleSelectModeInput(code: string, key: string): void {
}
}
/** Handles yes/no confirmation for delete-account admin flow. */
function handleAdminUserDeleteConfirmModeInput(code: string, key: string): void {
if (!adminSelectedUsername || adminPendingUserAction !== 'delete_account') {
state.mode = 'adminUserList';
return;
}
const control = handleYesNoMenuInput(code, key, adminDeleteConfirmIndex);
if (control.type === 'move') {
adminDeleteConfirmIndex = control.index;
updateStatus(`Delete account ${adminSelectedUsername}? ${YES_NO_OPTIONS[adminDeleteConfirmIndex].label}.`);
audio.sfxUiBlip();
return;
}
if (control.type === 'cancel') {
state.mode = 'adminUserList';
const selected = adminUsers[adminUserIndex];
if (selected) {
updateStatus(`${selected.username}, ${selected.role}, ${selected.status}.`);
} else {
updateStatus('Select user.');
}
audio.sfxUiCancel();
return;
}
if (control.type === 'select') {
const choice = YES_NO_OPTIONS[adminDeleteConfirmIndex];
if (choice.id === 'no') {
state.mode = 'adminUserList';
const selected = adminUsers[adminUserIndex];
if (selected) {
updateStatus(`${selected.username}, ${selected.role}, ${selected.status}.`);
} else {
updateStatus('Select user.');
}
audio.sfxUiCancel();
return;
}
adminPendingUserMutation = { action: 'delete_account', username: adminSelectedUsername };
signaling.send({ type: 'admin_user_delete', username: adminSelectedUsername });
}
}
/** Handles text edit for new-role creation from admin role list. */
function handleAdminRoleNameEditModeInput(code: string, key: string, ctrlKey: boolean): void {
const editAction = getEditSessionAction(code);
@@ -3475,6 +3542,7 @@ function setupInputHandlers(): void {
adminRoleDeleteReplacement: (currentCode, currentKey) => handleAdminRoleDeleteReplacementModeInput(currentCode, currentKey),
adminUserList: (currentCode, currentKey) => handleAdminUserListModeInput(currentCode, currentKey),
adminUserRoleSelect: (currentCode, currentKey) => handleAdminUserRoleSelectModeInput(currentCode, currentKey),
adminUserDeleteConfirm: (currentCode, currentKey) => handleAdminUserDeleteConfirmModeInput(currentCode, currentKey),
adminRoleNameEdit: (currentCode, currentKey, currentCtrlKey) =>
handleAdminRoleNameEditModeInput(currentCode, currentKey, currentCtrlKey),
itemProperties: (currentCode, currentKey) => itemPropertyEditor.handleItemPropertiesModeInput(currentCode, currentKey),

View File

@@ -315,6 +315,7 @@ export const adminActionResultSchema = z.object({
'user_set_role',
'user_ban',
'user_unban',
'user_delete',
]),
message: z.string(),
});
@@ -355,10 +356,11 @@ export type OutgoingMessage =
| { type: 'admin_role_create'; name: string }
| { type: 'admin_role_update_permissions'; role: string; permissions: string[] }
| { type: 'admin_role_delete'; role: string; replacementRole: string }
| { type: 'admin_users_list'; action?: 'set_role' | 'ban' | 'unban' }
| { type: 'admin_users_list'; action?: 'set_role' | 'ban' | 'unban' | 'delete_account' }
| { type: 'admin_user_set_role'; username: string; role: string }
| { type: 'admin_user_ban'; username: string }
| { type: 'admin_user_unban'; username: string }
| { type: 'admin_user_delete'; username: string }
| { type: 'signal'; targetId: string; sdp?: RTCSessionDescriptionInit; ice?: RTCIceCandidateInit }
| { type: 'update_position'; x: number; y: number }
| { type: 'teleport_complete'; x: number; y: number }

View File

@@ -48,6 +48,7 @@ export type GameMode =
| 'adminRoleDeleteReplacement'
| 'adminUserList'
| 'adminUserRoleSelect'
| 'adminUserDeleteConfirm'
| 'adminRoleNameEdit'
| 'pianoUse';