This commit is contained in:
zhx 2026-06-25 19:11:22 +08:00
parent 3fa9222b99
commit 9a9dfce9ba
3 changed files with 379 additions and 240 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>Manager Center</title>
<link rel="stylesheet" href="./style.css?v=20260624-0100" />
<link rel="stylesheet" href="./style.css?v=20260625-0100" />
</head>
<body>
<div class="manager-center" aria-label="Manager Center" data-i18n-aria="page_label" data-loading="true">
@ -39,35 +39,8 @@
</div>
</section>
<section class="card search-card">
<div class="section-head">
<h2 data-i18n="user">User</h2>
</div>
<form class="search-form" id="searchForm" autocomplete="off">
<input id="accountInput" type="text" inputmode="text" placeholder="Enter user ID or account" data-i18n-placeholder="user_search_placeholder" />
<button id="searchButton" type="submit" data-i18n="search">Search</button>
</form>
<div class="form-status" id="searchStatus" role="status" hidden></div>
</section>
<section class="card target-card" id="targetCard" hidden>
<div class="avatar-shell">
<img id="targetAvatar" alt="" hidden />
<span id="targetAvatarFallback">U</span>
</div>
<div class="profile-copy">
<div class="eyebrow" data-i18n="target_user">Target User</div>
<div class="name" id="targetName">-</div>
<div class="meta" id="targetUid">UID: -</div>
<div class="pill-row">
<span class="status-pill" id="targetAccountStatus">-</span>
<span class="status-pill" id="targetCountryStatus">-</span>
</div>
</div>
</section>
<section class="card action-card" id="actionCard" hidden>
<button class="action-item" id="addBDLeaderButton" type="button" disabled>
<section class="card action-card" id="actionCard" aria-label="Manager actions">
<button class="action-item" id="addBDLeaderButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
@ -83,7 +56,7 @@
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item" id="countryEntryButton" type="button" disabled>
<button class="action-item" id="countryEntryButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
@ -116,7 +89,7 @@
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item is-danger" id="banEntryButton" type="button" disabled>
<button class="action-item is-danger" id="banEntryButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
@ -130,7 +103,7 @@
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item" id="unbanEntryButton" type="button" disabled>
<button class="action-item" id="unbanEntryButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M12 3 20 7v5c0 5-3.4 8-8 9-4.6-1-8-4-8-9V7l8-4Z" />
@ -144,25 +117,31 @@
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item" id="bdLeaderListButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</span>
<span class="action-item-copy">
<span class="action-item-title" data-i18n="bd_leaders">BD Leaders</span>
<span class="action-item-subtitle" id="bdLeaderSummary">0</span>
</span>
<svg class="action-item-arrow" viewBox="0 0 24 24" aria-hidden="true">
<path d="m9 6 6 6-6 6" />
</svg>
</button>
</section>
<section class="card bd-leader-card">
<div class="section-head">
<div>
<h2 data-i18n="bd_leaders">BD Leaders</h2>
<div class="section-subtitle" id="bdLeaderCount">0</div>
</div>
<button class="text-button" id="refreshBDLeaderButton" type="button" data-i18n="refresh">Refresh</button>
</div>
<div class="form-status" id="bdLeaderStatus" role="status" hidden></div>
<div class="bd-leader-list" id="bdLeaderList"></div>
</section>
<div class="form-status" id="pageStatus" role="status" hidden></div>
</main>
<div class="modal-mask" id="countryModal" role="dialog" aria-modal="true" aria-labelledby="countryModalTitle" hidden>
<div class="modal-mask" id="actionModal" role="dialog" aria-modal="true" aria-labelledby="actionModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="countryModalTitle" data-i18n="change_country">Change Country</h2>
<h2 id="actionModalTitle">-</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
@ -170,106 +149,111 @@
</button>
</div>
<div class="modal-body">
<label class="field-label" id="countryLabelText" data-i18n="select_country">Select country</label>
<div class="current-country" id="currentCountryText">-</div>
<div class="custom-select" id="countrySelect" data-value="">
<button
class="custom-select-button"
id="countryButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="countryLabelText countrySelectedLabel"
>
<span id="countrySelectedLabel">-</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
</svg>
</button>
<div class="custom-select-menu" id="countryMenu" role="listbox" aria-labelledby="countryLabelText" hidden></div>
</div>
<button class="primary-action" id="changeCountryButton" type="button" disabled data-i18n="change_country">Change Country</button>
<div class="form-status" id="countryStatus" role="status" hidden></div>
</div>
</section>
</div>
<form class="search-form modal-search-form" id="modalSearchForm" autocomplete="off">
<input id="modalAccountInput" type="text" inputmode="text" placeholder="Enter user ID or account" data-i18n-placeholder="user_search_placeholder" />
<button id="modalSearchButton" type="submit" data-i18n="search">Search</button>
</form>
<div class="form-status" id="modalSearchStatus" role="status" hidden></div>
<div class="modal-mask" id="giftModal" role="dialog" aria-modal="true" aria-labelledby="giftModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="giftModalTitle" data-i18n="gift_props">Gift Props</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<label class="field-label" id="propsTypeLabelText" data-i18n="gift_type">Gift Type</label>
<div class="custom-select" id="propsTypeSelect" data-value="RIDE">
<button
class="custom-select-button"
id="propsTypeButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="propsTypeLabelText propsTypeSelectedLabel"
>
<span id="propsTypeSelectedLabel">Ride</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
</svg>
</button>
<div class="custom-select-menu" id="propsTypeMenu" role="listbox" aria-labelledby="propsTypeLabelText" hidden>
<button type="button" role="option" data-value="RIDE" data-i18n="prop_type_ride">Ride</button>
<button type="button" role="option" data-value="NOBLE_VIP" data-i18n="prop_type_noble_vip">Noble VIP</button>
<button type="button" role="option" data-value="AVATAR_FRAME" data-i18n="prop_type_avatar_frame">Avatar Frame</button>
<button type="button" role="option" data-value="BADGE" data-i18n="prop_type_badge">Badge</button>
<button type="button" role="option" data-value="CHAT_BUBBLE" data-i18n="prop_type_chat_bubble">Chat Bubble</button>
<button type="button" role="option" data-value="DATA_CARD" data-i18n="prop_type_data_card">Profile Card</button>
<section class="target-card modal-target-card" id="targetCard" hidden>
<div class="avatar-shell">
<img id="targetAvatar" alt="" hidden />
<span id="targetAvatarFallback">U</span>
</div>
<div class="profile-copy">
<div class="eyebrow" data-i18n="target_user">Target User</div>
<div class="name" id="targetName">-</div>
<div class="meta" id="targetUid">UID: -</div>
<div class="pill-row">
<span class="status-pill" id="targetAccountStatus">-</span>
<span class="status-pill" id="targetCountryStatus">-</span>
</div>
</div>
</section>
<div class="operation-panel" id="addBDLeaderPanel" hidden>
<button class="primary-action" id="addBDLeaderConfirmButton" type="button" disabled data-i18n="add_bd_leader">Add BD Leader</button>
<div class="form-status" id="addBDLeaderStatus" role="status" hidden></div>
</div>
<div class="props-status" id="propsStatus" role="status" hidden></div>
<div class="props-list" id="propsList"></div>
<button class="primary-action" id="sendPropsButton" type="button" disabled data-i18n="send">Send</button>
</div>
</section>
</div>
<div class="modal-mask" id="banModal" role="dialog" aria-modal="true" aria-labelledby="banModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="banModalTitle" data-i18n="ban_user">Ban User</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<span class="danger-chip" id="banEligibility">-</span>
<textarea id="banReason" maxlength="200" rows="3" placeholder="Ban reason" data-i18n-placeholder="ban_reason"></textarea>
<button class="danger-action" id="banButton" type="button" disabled data-i18n="ban">Ban</button>
<div class="form-status" id="banStatus" role="status" hidden></div>
</div>
</section>
</div>
<div class="operation-panel" id="countryPanel" hidden>
<label class="field-label" id="countryLabelText" data-i18n="select_country">Select country</label>
<div class="current-country" id="currentCountryText">-</div>
<div class="custom-select" id="countrySelect" data-value="">
<button
class="custom-select-button"
id="countryButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="countryLabelText countrySelectedLabel"
>
<span id="countrySelectedLabel">-</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
</svg>
</button>
<div class="custom-select-menu" id="countryMenu" role="listbox" aria-labelledby="countryLabelText" hidden></div>
</div>
<button class="primary-action" id="changeCountryButton" type="button" disabled data-i18n="change_country">Change Country</button>
<div class="form-status" id="countryStatus" role="status" hidden></div>
</div>
<div class="modal-mask" id="unbanModal" role="dialog" aria-modal="true" aria-labelledby="unbanModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="unbanModalTitle" data-i18n="unban_user">Unban User</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<span class="danger-chip" id="unbanEligibility">-</span>
<textarea id="unbanReason" maxlength="200" rows="3" placeholder="Unban reason" data-i18n-placeholder="unban_reason"></textarea>
<button class="primary-action" id="unbanButton" type="button" disabled data-i18n="unban">Unban</button>
<div class="form-status" id="unbanStatus" role="status" hidden></div>
<div class="operation-panel" id="giftPanel" hidden>
<label class="field-label" id="propsTypeLabelText" data-i18n="gift_type">Gift Type</label>
<div class="custom-select" id="propsTypeSelect" data-value="RIDE">
<button
class="custom-select-button"
id="propsTypeButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="propsTypeLabelText propsTypeSelectedLabel"
>
<span id="propsTypeSelectedLabel">Ride</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
</svg>
</button>
<div class="custom-select-menu" id="propsTypeMenu" role="listbox" aria-labelledby="propsTypeLabelText" hidden>
<button type="button" role="option" data-value="RIDE" data-i18n="prop_type_ride">Ride</button>
<button type="button" role="option" data-value="NOBLE_VIP" data-i18n="prop_type_noble_vip">Noble VIP</button>
<button type="button" role="option" data-value="AVATAR_FRAME" data-i18n="prop_type_avatar_frame">Avatar Frame</button>
<button type="button" role="option" data-value="BADGE" data-i18n="prop_type_badge">Badge</button>
<button type="button" role="option" data-value="CHAT_BUBBLE" data-i18n="prop_type_chat_bubble">Chat Bubble</button>
<button type="button" role="option" data-value="DATA_CARD" data-i18n="prop_type_data_card">Profile Card</button>
</div>
</div>
<div class="props-status" id="propsStatus" role="status" hidden></div>
<div class="props-list" id="propsList"></div>
<button class="primary-action" id="sendPropsButton" type="button" disabled data-i18n="send">Send</button>
</div>
<div class="operation-panel" id="banPanel" hidden>
<span class="danger-chip" id="banEligibility">-</span>
<textarea id="banReason" maxlength="200" rows="3" placeholder="Ban reason" data-i18n-placeholder="ban_reason"></textarea>
<button class="danger-action" id="banButton" type="button" disabled data-i18n="ban">Ban</button>
<div class="form-status" id="banStatus" role="status" hidden></div>
</div>
<div class="operation-panel" id="unbanPanel" hidden>
<span class="danger-chip" id="unbanEligibility">-</span>
<textarea id="unbanReason" maxlength="200" rows="3" placeholder="Unban reason" data-i18n-placeholder="unban_reason"></textarea>
<button class="primary-action" id="unbanButton" type="button" disabled data-i18n="unban">Unban</button>
<div class="form-status" id="unbanStatus" role="status" hidden></div>
</div>
<div class="operation-panel" id="bdLeaderPanel" hidden>
<div class="section-head">
<div>
<h2 data-i18n="bd_leaders">BD Leaders</h2>
<div class="section-subtitle" id="bdLeaderCount">0</div>
</div>
<button class="text-button" id="refreshBDLeaderButton" type="button" data-i18n="refresh">Refresh</button>
</div>
<div class="form-status" id="bdLeaderStatus" role="status" hidden></div>
<div class="bd-leader-list" id="bdLeaderList"></div>
</div>
</div>
</section>
</div>
@ -280,6 +264,6 @@
</div>
<div class="toast" id="toast" role="status" hidden></div>
</div>
<script src="./script.js?v=20260624-0100" defer></script>
<script src="./script.js?v=20260625-0100" defer></script>
</body>
</html>

View File

@ -349,6 +349,7 @@
const state = {
manager: null,
target: null,
activeAction: "",
features: defaultFeatures(),
propsType: defaultPropsType,
props: [],
@ -363,6 +364,54 @@
addingBDLeader: false
};
const actionConfigs = {
addBDLeader: {
titleKey: "add_bd_leader",
disabledKey: "add_bd_leader_disabled",
feature: "addBdLeader",
panel: "#addBDLeaderPanel",
status: "#addBDLeaderStatus",
needsUser: true
},
changeCountry: {
titleKey: "change_country",
disabledKey: "change_country_disabled",
feature: "changeCountry",
panel: "#countryPanel",
status: "#countryStatus",
needsUser: true
},
giftProps: {
titleKey: "gift_props",
disabledKey: "gift_props_disabled",
feature: "giftProps",
panel: "#giftPanel",
status: "#propsStatus",
needsUser: true
},
banUser: {
titleKey: "ban_user",
disabledKey: "ban_user_disabled",
feature: "banUser",
panel: "#banPanel",
status: "#banStatus",
needsUser: true
},
unbanUser: {
titleKey: "unban_user",
disabledKey: "unban_user_disabled",
feature: "unbanUser",
panel: "#unbanPanel",
status: "#unbanStatus",
needsUser: true
},
bdLeaders: {
titleKey: "bd_leaders",
panel: "#bdLeaderPanel",
needsUser: false
}
};
const params = readURLParams();
let currentLang = normalizeLanguage(params.get("lang"));
let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en;
@ -414,7 +463,7 @@
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
try {
const response = await fetch(`./locales/${normalizedLang}.json?v=20260624-0100`, { cache: "no-store" });
const response = await fetch(`./locales/${normalizedLang}.json?v=20260625-0100`, { cache: "no-store" });
if (response.ok) return await response.json();
} catch (error) {
return fallbackMessages[normalizedLang];
@ -570,6 +619,7 @@
renderCountrySelect();
renderManager(state.manager);
renderTarget();
renderActionModal();
renderProps();
renderBDLeaders();
updateActionState();
@ -723,6 +773,47 @@
document.body.classList.toggle("is-modal-open", Boolean(document.querySelector(".modal-mask:not([hidden])")));
}
function activeConfig() {
return actionConfigs[state.activeAction] || null;
}
function clearActionStatuses() {
setStatus($("#modalSearchStatus"), "");
Object.values(actionConfigs).forEach((config) => {
if (config.status) setStatus($(config.status), "");
});
}
function clearActionModalForm() {
const input = $("#modalAccountInput");
if (input) input.value = "";
const banReason = $("#banReason");
const unbanReason = $("#unbanReason");
if (banReason) banReason.value = "";
if (unbanReason) unbanReason.value = "";
state.target = null;
state.selectedCountryId = "";
renderTarget();
clearActionStatuses();
}
function renderActionModal() {
const config = activeConfig();
const title = $("#actionModalTitle");
if (title) title.textContent = config ? message(config.titleKey) : "-";
const needsUser = config?.needsUser !== false;
const searchForm = $("#modalSearchForm");
const modalTarget = $("#targetCard");
if (searchForm) searchForm.hidden = !needsUser;
if (modalTarget && !needsUser) modalTarget.hidden = true;
Object.values(actionConfigs).forEach((item) => {
const panel = $(item.panel);
if (panel) panel.hidden = item !== config || (item.needsUser !== false && !state.target);
});
}
function setModalOpen(selector, open) {
const modal = $(selector);
if (!modal) return;
@ -739,77 +830,56 @@
document.querySelectorAll(".modal-mask").forEach((modal) => {
modal.hidden = true;
});
state.activeAction = "";
state.target = null;
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
renderTarget();
syncModalState();
}
function openGiftModal() {
if (!featureEnabled("giftProps")) {
showToast(message("gift_props_disabled"));
function openActionModal(action) {
const config = actionConfigs[action];
if (!config) return;
if (config.feature && !featureEnabled(config.feature)) {
showToast(message(config.disabledKey));
return;
}
if (!state.target) {
showToast(message("enter_user"));
return;
}
setModalOpen("#giftModal", true);
state.activeAction = action;
clearActionModalForm();
renderActionModal();
setModalOpen("#actionModal", true);
updateActionState();
if (action === "giftProps" && !state.props.length) {
loadProps().catch((error) => {
setStatus($("#propsStatus"), friendlyError(error), "error");
});
}
if (action === "bdLeaders") {
renderBDLeaders();
}
window.setTimeout(() => {
if (config.needsUser !== false) $("#modalAccountInput")?.focus();
}, 0);
}
async function openCountryModal() {
if (!featureEnabled("changeCountry")) {
showToast(message("change_country_disabled"));
return;
}
if (!state.target) {
showToast(message("enter_user"));
return;
}
function openGiftModal() {
openActionModal("giftProps");
}
setStatus($("#countryStatus"), "");
const currentCountryText = $("#currentCountryText");
if (currentCountryText) currentCountryText.textContent = `${message("current_country")}: ${displayCountryText(state.target)}`;
state.selectedCountryId = String(state.target.countryId || matchingCountryId(state.target) || "");
setModalOpen("#countryModal", true);
renderCountrySelect();
updateActionState();
if (!state.countries.length) {
try {
await loadCountries();
} catch (error) {
setStatus($("#countryStatus"), friendlyError(error), "error");
}
}
function openCountryModal() {
openActionModal("changeCountry");
}
function openBanModal() {
if (!featureEnabled("banUser")) {
showToast(message("ban_user_disabled"));
return;
}
if (!state.target) {
showToast(message("enter_user"));
return;
}
setStatus($("#banStatus"), "");
setModalOpen("#banModal", true);
updateActionState();
openActionModal("banUser");
}
function openUnbanModal() {
if (!featureEnabled("unbanUser")) {
showToast(message("unban_user_disabled"));
return;
}
if (!state.target) {
showToast(message("enter_user"));
return;
}
setStatus($("#unbanStatus"), "");
setModalOpen("#unbanModal", true);
updateActionState();
openActionModal("unbanUser");
}
function closePage() {
@ -943,6 +1013,13 @@
return message("bd_leader_count", "{count} BD Leaders").replace("{count}", new Intl.NumberFormat("en-US").format(Number(count) || 0));
}
function formatBDLeaderSummary() {
const countText = formatBDLeaderCount(state.bdLeaders.length);
const firstLeader = state.bdLeaders[0];
if (!firstLeader) return countText;
return `${countText} - ${userIDText(firstLeader)}`;
}
function formatDateTime(value) {
if (!value) return "";
const date = new Date(value);
@ -973,18 +1050,15 @@
function renderTarget() {
const card = $("#targetCard");
const actionCard = $("#actionCard");
if (!state.target) {
if (card) card.hidden = true;
if (actionCard) actionCard.hidden = true;
closeModals();
renderActionModal();
updateActionState();
return;
}
const target = state.target;
if (card) card.hidden = false;
if (actionCard) actionCard.hidden = false;
$("#targetName").textContent = displayName(target);
$("#targetUid").textContent = userIDText(target);
$("#targetAccountStatus").textContent = target.accountStatus || "-";
@ -1002,6 +1076,28 @@
unbanEligibility.classList.toggle("is-danger", !canUnban);
}
renderAvatar("#targetAvatar", "#targetAvatarFallback", target.userAvatar, displayName(target), "U");
renderActionModal();
updateActionState();
}
async function prepareActiveActionAfterTarget() {
if (!state.target) return;
if (state.activeAction === "changeCountry") {
const currentCountryText = $("#currentCountryText");
if (currentCountryText) currentCountryText.textContent = `${message("current_country")}: ${displayCountryText(state.target)}`;
state.selectedCountryId = String(state.target.countryId || matchingCountryId(state.target) || "");
renderCountrySelect();
if (!state.countries.length) {
await loadCountries();
}
return;
}
if (state.activeAction === "giftProps") {
renderProps();
}
updateActionState();
}
@ -1071,7 +1167,10 @@
function renderBDLeaders() {
const list = $("#bdLeaderList");
const count = $("#bdLeaderCount");
if (count) count.textContent = formatBDLeaderCount(state.bdLeaders.length);
const summary = $("#bdLeaderSummary");
const leaderCountText = formatBDLeaderCount(state.bdLeaders.length);
if (count) count.textContent = leaderCountText;
if (summary) summary.textContent = formatBDLeaderSummary();
if (!list) return;
list.replaceChildren();
@ -1116,10 +1215,7 @@
name.textContent = displayName(leader);
const meta = document.createElement("span");
meta.className = "bd-leader-meta";
const metaItems = [userIDText(leader)];
if (leader.regionId) metaItems.push(leader.regionId);
if (leader.originSys) metaItems.push(leader.originSys);
meta.textContent = metaItems.join(" · ");
meta.textContent = userIDText(leader);
copy.append(name, meta);
const time = document.createElement("span");
@ -1138,14 +1234,16 @@
const giftEntryButton = $("#giftEntryButton");
const banEntryButton = $("#banEntryButton");
const unbanEntryButton = $("#unbanEntryButton");
const bdLeaderListButton = $("#bdLeaderListButton");
const refreshBDLeaderButton = $("#refreshBDLeaderButton");
const addBDLeaderConfirmButton = $("#addBDLeaderConfirmButton");
const sendButton = $("#sendPropsButton");
const changeCountryButton = $("#changeCountryButton");
const banButton = $("#banButton");
const unbanButton = $("#unbanButton");
if (addBDLeaderButton) {
const disabledByFeature = !featureEnabled("addBdLeader");
addBDLeaderButton.disabled = disabledByFeature || !state.target || state.addingBDLeader || isTargetBDLeader();
addBDLeaderButton.disabled = disabledByFeature || state.addingBDLeader;
addBDLeaderButton.classList.toggle("is-feature-disabled", disabledByFeature);
const title = addBDLeaderButton.querySelector(".action-item-title");
if (title) {
@ -1156,33 +1254,38 @@
}
if (giftEntryButton) {
const disabledByFeature = !featureEnabled("giftProps");
giftEntryButton.disabled = disabledByFeature || !state.target;
giftEntryButton.disabled = disabledByFeature;
giftEntryButton.classList.toggle("is-feature-disabled", disabledByFeature);
const title = giftEntryButton.querySelector(".action-item-title");
if (title) title.textContent = disabledByFeature ? message("gift_props_disabled") : message("gift_props");
}
if (countryEntryButton) {
const disabledByFeature = !featureEnabled("changeCountry");
countryEntryButton.disabled = disabledByFeature || !state.target || state.countriesLoading || state.changingCountry;
countryEntryButton.disabled = disabledByFeature || state.changingCountry;
countryEntryButton.classList.toggle("is-feature-disabled", disabledByFeature);
const title = countryEntryButton.querySelector(".action-item-title");
if (title) title.textContent = disabledByFeature ? message("change_country_disabled") : message("change_country");
}
if (banEntryButton) {
const disabledByFeature = !featureEnabled("banUser");
banEntryButton.disabled = disabledByFeature || !state.target;
banEntryButton.disabled = disabledByFeature;
banEntryButton.classList.toggle("is-feature-disabled", disabledByFeature);
const title = banEntryButton.querySelector(".action-item-title");
if (title) title.textContent = disabledByFeature ? message("ban_user_disabled") : message("ban_user");
}
if (unbanEntryButton) {
const disabledByFeature = !featureEnabled("unbanUser");
unbanEntryButton.disabled = disabledByFeature || !state.target;
unbanEntryButton.disabled = disabledByFeature;
unbanEntryButton.classList.toggle("is-feature-disabled", disabledByFeature);
const title = unbanEntryButton.querySelector(".action-item-title");
if (title) title.textContent = disabledByFeature ? message("unban_user_disabled") : message("unban_user");
}
if (bdLeaderListButton) bdLeaderListButton.disabled = state.bdLeadersLoading;
if (refreshBDLeaderButton) refreshBDLeaderButton.disabled = state.bdLeadersLoading;
if (addBDLeaderConfirmButton) {
addBDLeaderConfirmButton.disabled = !featureEnabled("addBdLeader") || !state.target || state.addingBDLeader || isTargetBDLeader();
addBDLeaderConfirmButton.textContent = state.addingBDLeader ? message("adding_bd_leader") : message("add_bd_leader");
}
if (sendButton) sendButton.disabled = !featureEnabled("giftProps") || !state.target || !state.selectedPropsId;
if (changeCountryButton) {
const currentID = String(state.target?.countryId || matchingCountryId(state.target) || "");
@ -1252,26 +1355,29 @@
async function searchUser(event) {
event.preventDefault();
const input = $("#accountInput");
const input = $("#modalAccountInput");
const status = $("#modalSearchStatus");
const button = $("#modalSearchButton");
const account = String(input?.value || "").trim();
if (!account) {
setStatus($("#searchStatus"), message("enter_user"), "error");
setStatus(status, message("enter_user"), "error");
return;
}
$("#searchButton").disabled = true;
setStatus($("#searchStatus"), message("searching"));
button.disabled = true;
setStatus(status, message("searching"));
try {
const body = await requestGoJSON(endpointSearchUser(account));
state.target = body?.user || body || null;
renderTarget();
setStatus($("#searchStatus"), "");
await prepareActiveActionAfterTarget();
setStatus(status, "");
} catch (error) {
state.target = null;
renderTarget();
setStatus($("#searchStatus"), friendlyError(error), "error");
setStatus(status, friendlyError(error), "error");
} finally {
$("#searchButton").disabled = false;
button.disabled = false;
}
}
@ -1280,13 +1386,18 @@
showToast(message("add_bd_leader_disabled"));
return;
}
if (!state.target || state.addingBDLeader) return;
if (!state.target) {
setStatus($("#addBDLeaderStatus"), message("enter_user"), "error");
return;
}
if (state.addingBDLeader) return;
if (isTargetBDLeader()) {
showToast(message("user_already_bd_leader"));
setStatus($("#addBDLeaderStatus"), message("user_already_bd_leader"), "error");
return;
}
state.addingBDLeader = true;
setStatus($("#addBDLeaderStatus"), message("adding_bd_leader"));
updateActionState();
try {
await requestGoJSON(endpointBDLeaders(), {
@ -1295,10 +1406,12 @@
userId: displayUserID(state.target)
}
});
setStatus($("#addBDLeaderStatus"), "");
closeModals();
showToast(message("add_bd_leader_success"));
await loadBDLeaders();
} catch (error) {
showToast(friendlyError(error));
setStatus($("#addBDLeaderStatus"), friendlyError(error), "error");
} finally {
state.addingBDLeader = false;
updateActionState();
@ -1310,7 +1423,11 @@
showToast(message("gift_props_disabled"));
return;
}
if (!state.target || !state.selectedPropsId) return;
if (!state.target) {
setStatus($("#propsStatus"), message("enter_user"), "error");
return;
}
if (!state.selectedPropsId) return;
const button = $("#sendPropsButton");
button.disabled = true;
@ -1322,7 +1439,7 @@
propsId: state.selectedPropsId
}
});
setModalOpen("#giftModal", false);
closeModals();
showToast(message("send_success"));
} catch (error) {
showToast(friendlyError(error));
@ -1336,6 +1453,10 @@
setStatus($("#banStatus"), message("ban_user_disabled"), "error");
return;
}
if (!state.target) {
setStatus($("#banStatus"), message("enter_user"), "error");
return;
}
if (!canBanTarget(state.target)) return;
const button = $("#banButton");
@ -1351,8 +1472,8 @@
});
state.target.accountStatus = "ARCHIVE";
renderTarget();
setModalOpen("#banModal", false);
setStatus($("#banStatus"), "");
closeModals();
showToast(message("ban_success"));
} catch (error) {
setStatus($("#banStatus"), friendlyError(error), "error");
@ -1366,6 +1487,10 @@
setStatus($("#unbanStatus"), message("unban_user_disabled"), "error");
return;
}
if (!state.target) {
setStatus($("#unbanStatus"), message("enter_user"), "error");
return;
}
if (!canUnbanTarget(state.target)) return;
const button = $("#unbanButton");
@ -1381,8 +1506,8 @@
});
state.target.accountStatus = "NORMAL";
renderTarget();
setModalOpen("#unbanModal", false);
setStatus($("#unbanStatus"), "");
closeModals();
showToast(message("unban_success"));
} catch (error) {
setStatus($("#unbanStatus"), friendlyError(error), "error");
@ -1392,7 +1517,10 @@
}
async function changeUserCountry() {
if (!state.target) return;
if (!state.target) {
setStatus($("#countryStatus"), message("enter_user"), "error");
return;
}
if (!featureEnabled("changeCountry")) {
setStatus($("#countryStatus"), message("change_country_disabled"), "error");
return;
@ -1415,8 +1543,8 @@
});
state.target = body?.user || state.target;
renderTarget();
setModalOpen("#countryModal", false);
setStatus($("#countryStatus"), "");
closeModals();
showToast(message("country_changed"));
} catch (error) {
setStatus($("#countryStatus"), friendlyError(error), "error");
@ -1467,7 +1595,8 @@
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
});
$("#addBDLeaderButton")?.addEventListener("click", addBDLeader);
$("#addBDLeaderButton")?.addEventListener("click", () => openActionModal("addBDLeader"));
$("#bdLeaderListButton")?.addEventListener("click", () => openActionModal("bdLeaders"));
$("#refreshBDLeaderButton")?.addEventListener("click", () => {
loadBDLeaders().catch((error) => {
showToast(friendlyError(error));
@ -1510,7 +1639,8 @@
setCountryMenuOpen(false);
closeModals();
});
$("#searchForm")?.addEventListener("submit", searchUser);
$("#modalSearchForm")?.addEventListener("submit", searchUser);
$("#addBDLeaderConfirmButton")?.addEventListener("click", addBDLeader);
$("#sendPropsButton")?.addEventListener("click", sendProps);
$("#banButton")?.addEventListener("click", banUser);
$("#unbanButton")?.addEventListener("click", unbanUser);
@ -1523,7 +1653,7 @@
await applyLanguage(currentLang);
if (!readRawParam("token")) {
setLoading(false);
setStatus($("#searchStatus"), message("missing_token"), "error");
setStatus($("#pageStatus"), message("missing_token"), "error");
return;
}

View File

@ -278,19 +278,22 @@ button:disabled {
.action-card {
display: grid;
gap: 4px;
padding: 6px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 12px;
}
.action-item {
position: relative;
display: grid;
grid-template-columns: 42px minmax(0, 1fr) 24px;
gap: 10px;
grid-template-columns: 44px minmax(0, 1fr);
gap: 8px;
align-items: center;
width: 100%;
min-height: 58px;
min-height: 112px;
border: 1px solid var(--line);
border-radius: 6px;
padding: 8px;
padding: 12px;
background: #fff;
color: var(--text);
text-align: left;
@ -313,8 +316,8 @@ button:disabled {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
width: 44px;
height: 44px;
border-radius: 8px;
background: #e7f8f4;
color: var(--primary-dark);
@ -338,6 +341,8 @@ button:disabled {
.action-item-copy {
display: grid;
grid-column: 1 / -1;
gap: 4px;
min-width: 0;
}
@ -349,8 +354,19 @@ button:disabled {
white-space: nowrap;
}
.action-item-subtitle {
overflow: hidden;
color: var(--muted);
font-size: 12px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.action-item-arrow {
justify-self: end;
position: absolute;
top: 14px;
right: 12px;
color: #9aa4af;
}
@ -394,6 +410,10 @@ button:disabled {
.bd-leader-list {
display: grid;
gap: 8px;
max-height: min(54vh, 420px);
overflow-y: auto;
padding-right: 2px;
overscroll-behavior: contain;
}
.bd-leader-row {
@ -814,6 +834,11 @@ textarea:focus {
margin-bottom: 10px;
}
.modal-target-card,
.operation-panel {
margin-top: 12px;
}
.modal-body textarea {
display: block;
}