diff --git a/h5/hyapp/manager-center/index.html b/h5/hyapp/manager-center/index.html index 10408f6..2c36780 100644 --- a/h5/hyapp/manager-center/index.html +++ b/h5/hyapp/manager-center/index.html @@ -4,7 +4,7 @@ Manager Center - +
@@ -61,6 +61,7 @@
UID: -
- + -
@@ -82,6 +83,22 @@ + + + + + + - + diff --git a/h5/hyapp/manager-center/locales/ar.json b/h5/hyapp/manager-center/locales/ar.json index 5a5cb82..a09b05c 100644 --- a/h5/hyapp/manager-center/locales/ar.json +++ b/h5/hyapp/manager-center/locales/ar.json @@ -11,6 +11,12 @@ "search": "بحث", "searching": "جار البحث...", "target_user": "المستخدم المستهدف", + "current_country": "Current country", + "change_country": "Change Country", + "select_country": "Select country", + "changing_country": "Changing...", + "country_changed": "Country changed", + "no_countries": "No countries available", "bd_leaders": "قادة BD", "bd_leader_count": "{count} قادة BD", "refresh": "تحديث", @@ -65,6 +71,9 @@ "user_is_admin": "لا يمكن إضافة مستخدم إداري كقائد BD", "user_already_bd_leader": "هذا المستخدم قائد BD بالفعل", "user_already_bound_bd_leader": "هذا المستخدم مرتبط بالفعل بقائد BD", + "user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + "country_not_found": "Country not found", + "country_required": "Select country", "prop_not_giftable": "هذا العنصر غير متاح كهديّة من المدير", "vip_not_giftable": "لا يمكن إهداء عنصر VIP هذا", "manager_feature_disabled": "تم تعطيل هذه الميزة من الإدارة", diff --git a/h5/hyapp/manager-center/locales/en.json b/h5/hyapp/manager-center/locales/en.json index d4ba138..274b861 100644 --- a/h5/hyapp/manager-center/locales/en.json +++ b/h5/hyapp/manager-center/locales/en.json @@ -11,6 +11,12 @@ "search": "Search", "searching": "Searching...", "target_user": "Target User", + "current_country": "Current country", + "change_country": "Change Country", + "select_country": "Select country", + "changing_country": "Changing...", + "country_changed": "Country changed", + "no_countries": "No countries available", "bd_leaders": "BD Leaders", "bd_leader_count": "{count} BD Leaders", "refresh": "Refresh", @@ -65,6 +71,9 @@ "user_is_admin": "Admin users cannot be added as BD Leaders", "user_already_bd_leader": "This user is already a BD Leader", "user_already_bound_bd_leader": "This user is already bound to a BD Leader", + "user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + "country_not_found": "Country not found", + "country_required": "Select country", "prop_not_giftable": "This prop is not available for manager gifting", "vip_not_giftable": "This noble prop cannot be gifted", "manager_feature_disabled": "This feature is disabled by admin", diff --git a/h5/hyapp/manager-center/locales/id.json b/h5/hyapp/manager-center/locales/id.json index b170e32..ee27341 100644 --- a/h5/hyapp/manager-center/locales/id.json +++ b/h5/hyapp/manager-center/locales/id.json @@ -11,6 +11,12 @@ "search": "Cari", "searching": "Mencari...", "target_user": "Pengguna Target", + "current_country": "Current country", + "change_country": "Change Country", + "select_country": "Select country", + "changing_country": "Changing...", + "country_changed": "Country changed", + "no_countries": "No countries available", "bd_leaders": "BD Leader", "bd_leader_count": "{count} BD Leader", "refresh": "Segarkan", @@ -65,6 +71,9 @@ "user_is_admin": "Pengguna admin tidak dapat ditambahkan sebagai BD Leader", "user_already_bd_leader": "Pengguna ini sudah menjadi BD Leader", "user_already_bound_bd_leader": "Pengguna ini sudah terhubung ke BD Leader", + "user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + "country_not_found": "Country not found", + "country_required": "Select country", "prop_not_giftable": "Properti ini tidak tersedia untuk hadiah manajer", "vip_not_giftable": "Properti noble ini tidak dapat dikirim", "manager_feature_disabled": "Fitur ini dinonaktifkan oleh admin", diff --git a/h5/hyapp/manager-center/locales/tr.json b/h5/hyapp/manager-center/locales/tr.json index b923f5e..dde896b 100644 --- a/h5/hyapp/manager-center/locales/tr.json +++ b/h5/hyapp/manager-center/locales/tr.json @@ -11,6 +11,12 @@ "search": "Ara", "searching": "Aranıyor...", "target_user": "Hedef Kullanıcı", + "current_country": "Current country", + "change_country": "Change Country", + "select_country": "Select country", + "changing_country": "Changing...", + "country_changed": "Country changed", + "no_countries": "No countries available", "bd_leaders": "BD Liderleri", "bd_leader_count": "{count} BD Lideri", "refresh": "Yenile", @@ -65,6 +71,9 @@ "user_is_admin": "Admin kullanıcılar BD lideri eklenemez", "user_already_bd_leader": "Bu kullanıcı zaten BD lideri", "user_already_bound_bd_leader": "Bu kullanıcı zaten bir BD liderine bağlı", + "user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + "country_not_found": "Country not found", + "country_required": "Select country", "prop_not_giftable": "Bu eşya yönetici hediyesi için açık değil", "vip_not_giftable": "Bu noble eşyası gönderilemez", "manager_feature_disabled": "Bu özellik admin tarafından kapatıldı", diff --git a/h5/hyapp/manager-center/script.js b/h5/hyapp/manager-center/script.js index 3162ff3..2c1301d 100644 --- a/h5/hyapp/manager-center/script.js +++ b/h5/hyapp/manager-center/script.js @@ -25,6 +25,12 @@ search: "Search", searching: "Searching...", target_user: "Target User", + current_country: "Current country", + change_country: "Change Country", + select_country: "Select country", + changing_country: "Changing...", + country_changed: "Country changed", + no_countries: "No countries available", bd_leaders: "BD Leaders", bd_leader_count: "{count} BD Leaders", refresh: "Refresh", @@ -79,6 +85,9 @@ user_is_admin: "Admin users cannot be added as BD Leaders", user_already_bd_leader: "This user is already a BD Leader", user_already_bound_bd_leader: "This user is already bound to a BD Leader", + user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + country_not_found: "Country not found", + country_required: "Select country", prop_not_giftable: "This prop is not available for manager gifting", vip_not_giftable: "This noble prop cannot be gifted", manager_feature_disabled: "This feature is disabled by admin", @@ -98,6 +107,12 @@ search: "بحث", searching: "جار البحث...", target_user: "المستخدم المستهدف", + current_country: "Current country", + change_country: "Change Country", + select_country: "Select country", + changing_country: "Changing...", + country_changed: "Country changed", + no_countries: "No countries available", bd_leaders: "قادة BD", bd_leader_count: "{count} قادة BD", refresh: "تحديث", @@ -152,6 +167,9 @@ user_is_admin: "لا يمكن إضافة مستخدم إداري كقائد BD", user_already_bd_leader: "هذا المستخدم قائد BD بالفعل", user_already_bound_bd_leader: "هذا المستخدم مرتبط بالفعل بقائد BD", + user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + country_not_found: "Country not found", + country_required: "Select country", prop_not_giftable: "هذا العنصر غير متاح كهديّة من المدير", vip_not_giftable: "لا يمكن إهداء عنصر VIP هذا", manager_feature_disabled: "تم تعطيل هذه الميزة من الإدارة", @@ -171,6 +189,12 @@ search: "Ara", searching: "Aranıyor...", target_user: "Hedef Kullanıcı", + current_country: "Current country", + change_country: "Change Country", + select_country: "Select country", + changing_country: "Changing...", + country_changed: "Country changed", + no_countries: "No countries available", bd_leaders: "BD Liderleri", bd_leader_count: "{count} BD Lideri", refresh: "Yenile", @@ -225,6 +249,9 @@ user_is_admin: "Admin kullanıcılar BD lideri eklenemez", user_already_bd_leader: "Bu kullanıcı zaten BD lideri", user_already_bound_bd_leader: "Bu kullanıcı zaten bir BD liderine bağlı", + user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + country_not_found: "Country not found", + country_required: "Select country", prop_not_giftable: "Bu eşya yönetici hediyesi için açık değil", vip_not_giftable: "Bu noble eşyası gönderilemez", manager_feature_disabled: "Bu özellik admin tarafından kapatıldı", @@ -244,6 +271,12 @@ search: "Cari", searching: "Mencari...", target_user: "Pengguna Target", + current_country: "Current country", + change_country: "Change Country", + select_country: "Select country", + changing_country: "Changing...", + country_changed: "Country changed", + no_countries: "No countries available", bd_leaders: "BD Leader", bd_leader_count: "{count} BD Leader", refresh: "Segarkan", @@ -298,6 +331,9 @@ user_is_admin: "Pengguna admin tidak dapat ditambahkan sebagai BD Leader", user_already_bd_leader: "Pengguna ini sudah menjadi BD Leader", user_already_bound_bd_leader: "Pengguna ini sudah terhubung ke BD Leader", + user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved", + country_not_found: "Country not found", + country_required: "Select country", prop_not_giftable: "Properti ini tidak tersedia untuk hadiah manajer", vip_not_giftable: "Properti noble ini tidak dapat dikirim", manager_feature_disabled: "Fitur ini dinonaktifkan oleh admin", @@ -313,6 +349,10 @@ propsType: defaultPropsType, props: [], selectedPropsId: "", + countries: [], + selectedCountryId: "", + countriesLoading: false, + changingCountry: false, bdLeaders: [], bdLeadersLoading: false, bdLeadersError: "", @@ -370,7 +410,7 @@ if (window.location.protocol === "file:") return fallbackMessages[normalizedLang]; try { - const response = await fetch(`./locales/${normalizedLang}.json?v=20260522-0101`, { cache: "no-store" }); + const response = await fetch(`./locales/${normalizedLang}.json?v=20260622-0100`, { cache: "no-store" }); if (response.ok) return await response.json(); } catch (error) { return fallbackMessages[normalizedLang]; @@ -438,6 +478,17 @@ button.setAttribute("aria-expanded", String(open)); } + function setCountryMenuOpen(open) { + const root = $("#countrySelect"); + const button = $("#countryButton"); + const menu = $("#countryMenu"); + if (!root || !button || !menu) return; + + root.classList.toggle("is-open", open); + menu.hidden = !open; + button.setAttribute("aria-expanded", String(open)); + } + function renderPropsTypeSelect() { const root = $("#propsTypeSelect"); const label = $("#propsTypeSelectedLabel"); @@ -451,9 +502,43 @@ }); } + function renderCountrySelect() { + const root = $("#countrySelect"); + const label = $("#countrySelectedLabel"); + const menu = $("#countryMenu"); + if (root) root.dataset.value = state.selectedCountryId || ""; + if (label) label.textContent = countryLabel(selectedCountry()) || message("select_country"); + if (!menu) return; + + menu.replaceChildren(); + state.countries.forEach((country) => { + const button = document.createElement("button"); + button.type = "button"; + button.setAttribute("role", "option"); + button.dataset.value = String(country.id); + button.textContent = countryLabel(country); + const isSelected = String(country.id) === String(state.selectedCountryId); + button.classList.toggle("is-selected", isSelected); + button.setAttribute("aria-selected", String(isSelected)); + button.addEventListener("click", (event) => { + event.stopPropagation(); + chooseCountry(country.id); + }); + menu.appendChild(button); + }); + } + + function chooseCountry(countryId) { + state.selectedCountryId = String(countryId || ""); + setCountryMenuOpen(false); + renderCountrySelect(); + updateActionState(); + } + async function choosePropsType(value) { const nextValue = String(value || defaultPropsType); setPropsTypeMenuOpen(false); + setCountryMenuOpen(false); if (nextValue === state.propsType) { renderPropsTypeSelect(); return; @@ -478,6 +563,7 @@ renderMessages(messages); renderLanguageControls(messages); renderPropsTypeSelect(); + renderCountrySelect(); renderManager(state.manager); renderTarget(); renderProps(); @@ -576,6 +662,14 @@ return `/app/h5/manager-center/users/search?account=${encodeURIComponent(account)}`; } + function endpointCountries() { + return "/app/h5/manager-center/countries"; + } + + function endpointChangeUserCountry() { + return "/app/h5/manager-center/users/country"; + } + function endpointProps(type) { return `/app/h5/manager-center/props?type=${encodeURIComponent(type || "")}`; } @@ -632,6 +726,7 @@ if (open) { setLanguageMenuOpen(false); setPropsTypeMenuOpen(false); + setCountryMenuOpen(false); } syncModalState(); } @@ -641,6 +736,7 @@ modal.hidden = true; }); setPropsTypeMenuOpen(false); + setCountryMenuOpen(false); syncModalState(); } @@ -657,6 +753,29 @@ updateActionState(); } + async function openCountryModal() { + if (!state.target) { + showToast(message("enter_user")); + return; + } + + 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 openBanModal() { if (!featureEnabled("banUser")) { showToast(message("ban_user_disabled")); @@ -775,6 +894,32 @@ return `${message("uid_label", "UID")}: ${displayShortID(user)}`; } + function displayCountryText(user) { + const name = String(user?.countryName || "").trim(); + const code = String(user?.countryCode || "").trim(); + if (name && code) return `${name} (${code})`; + return name || code || "-"; + } + + function countryLabel(country) { + if (!country) return ""; + const name = String(country.countryName || "").trim(); + const code = String(country.countryCode || "").trim(); + if (name && code) return `${name} (${code})`; + return name || code || ""; + } + + function selectedCountry() { + return state.countries.find((country) => String(country.id) === String(state.selectedCountryId)) || null; + } + + function matchingCountryId(user) { + const code = String(user?.countryCode || "").trim().toUpperCase(); + if (!code) return ""; + const country = state.countries.find((item) => String(item.countryCode || "").trim().toUpperCase() === code); + return country?.id || ""; + } + function propTypeLabel(type) { const key = `prop_type_${String(type || "").toLowerCase()}`; return message(key, type || "-"); @@ -833,6 +978,7 @@ $("#targetName").textContent = displayName(target); $("#targetUid").textContent = userIDText(target); $("#targetAccountStatus").textContent = target.accountStatus || "-"; + $("#targetCountryStatus").textContent = displayCountryText(target); const canBan = canBanTarget(target); const banEligibility = $("#banEligibility"); if (banEligibility) { @@ -978,11 +1124,13 @@ function updateActionState() { const addBDLeaderButton = $("#addBDLeaderButton"); + const countryEntryButton = $("#countryEntryButton"); const giftEntryButton = $("#giftEntryButton"); const banEntryButton = $("#banEntryButton"); const unbanEntryButton = $("#unbanEntryButton"); const refreshBDLeaderButton = $("#refreshBDLeaderButton"); const sendButton = $("#sendPropsButton"); + const changeCountryButton = $("#changeCountryButton"); const banButton = $("#banButton"); const unbanButton = $("#unbanButton"); if (addBDLeaderButton) { @@ -1003,6 +1151,11 @@ const title = giftEntryButton.querySelector(".action-item-title"); if (title) title.textContent = disabledByFeature ? message("gift_props_disabled") : message("gift_props"); } + if (countryEntryButton) { + countryEntryButton.disabled = !state.target || state.countriesLoading || state.changingCountry; + const title = countryEntryButton.querySelector(".action-item-title"); + if (title) title.textContent = message("change_country"); + } if (banEntryButton) { const disabledByFeature = !featureEnabled("banUser"); banEntryButton.disabled = disabledByFeature || !state.target; @@ -1019,6 +1172,11 @@ } if (refreshBDLeaderButton) refreshBDLeaderButton.disabled = state.bdLeadersLoading; if (sendButton) sendButton.disabled = !featureEnabled("giftProps") || !state.target || !state.selectedPropsId; + if (changeCountryButton) { + const currentID = String(state.target?.countryId || matchingCountryId(state.target) || ""); + changeCountryButton.disabled = !state.target || state.countriesLoading || state.changingCountry || !state.selectedCountryId || state.selectedCountryId === currentID; + changeCountryButton.textContent = state.changingCountry ? message("changing_country") : message("change_country"); + } if (banButton) banButton.disabled = !featureEnabled("banUser") || !canBanTarget(state.target); if (unbanButton) unbanButton.disabled = !featureEnabled("unbanUser") || !canUnbanTarget(state.target); } @@ -1047,6 +1205,22 @@ renderProps(); } + async function loadCountries() { + state.countriesLoading = true; + setStatus($("#countryStatus"), message("loading")); + updateActionState(); + try { + const body = await requestGoJSON(endpointCountries()); + state.countries = Array.isArray(body?.records) ? body.records : []; + if (!state.selectedCountryId) state.selectedCountryId = String(state.target?.countryId || matchingCountryId(state.target) || ""); + setStatus($("#countryStatus"), state.countries.length ? "" : message("no_countries")); + renderCountrySelect(); + } finally { + state.countriesLoading = false; + updateActionState(); + } + } + async function loadBDLeaders() { state.bdLeadersLoading = true; state.bdLeadersError = ""; @@ -1205,6 +1379,37 @@ } } + async function changeUserCountry() { + if (!state.target) return; + if (!state.selectedCountryId) { + setStatus($("#countryStatus"), message("country_required"), "error"); + return; + } + + state.changingCountry = true; + setStatus($("#countryStatus"), message("changing_country")); + updateActionState(); + try { + const body = await requestGoJSON(endpointChangeUserCountry(), { + method: "POST", + body: { + userId: displayUserID(state.target), + countryId: state.selectedCountryId + } + }); + state.target = body?.user || state.target; + renderTarget(); + setModalOpen("#countryModal", false); + setStatus($("#countryStatus"), ""); + showToast(message("country_changed")); + } catch (error) { + setStatus($("#countryStatus"), friendlyError(error), "error"); + } finally { + state.changingCountry = false; + updateActionState(); + } + } + function friendlyError(error) { const code = error?.code || error?.response?.code || error?.response?.errorCodeName || ""; if (code === "missing_token") return message("missing_token"); @@ -1218,6 +1423,9 @@ if (code === "user_is_admin") return message("user_is_admin"); if (code === "user_already_bd_leader") return message("user_already_bd_leader"); if (code === "user_already_bound_bd_leader") return message("user_already_bound_bd_leader"); + if (code === "user_identity_locked") return message("user_identity_locked"); + if (code === "country_not_found") return message("country_not_found"); + if (code === "country_required") return message("country_required"); if (code === "prop_not_giftable") return message("prop_not_giftable"); if (code === "vip_not_giftable") return message("vip_not_giftable"); if (code === "manager_feature_disabled") return message("manager_feature_disabled"); @@ -1228,6 +1436,7 @@ $(".back-button")?.addEventListener("click", closePage); $(".language-button")?.addEventListener("click", (event) => { event.stopPropagation(); + setCountryMenuOpen(false); setLanguageMenuOpen(Boolean($(".language-menu")?.hidden)); }); document.querySelectorAll(".language-menu [data-lang]").forEach((button) => { @@ -1240,6 +1449,7 @@ document.addEventListener("click", () => { setLanguageMenuOpen(false); setPropsTypeMenuOpen(false); + setCountryMenuOpen(false); }); $("#addBDLeaderButton")?.addEventListener("click", addBDLeader); $("#refreshBDLeaderButton")?.addEventListener("click", () => { @@ -1248,6 +1458,7 @@ }); }); $("#giftEntryButton")?.addEventListener("click", openGiftModal); + $("#countryEntryButton")?.addEventListener("click", openCountryModal); $("#banEntryButton")?.addEventListener("click", openBanModal); $("#unbanEntryButton")?.addEventListener("click", openUnbanModal); document.querySelectorAll("[data-modal-close]").forEach((button) => { @@ -1261,6 +1472,7 @@ $("#propsTypeButton")?.addEventListener("click", (event) => { event.stopPropagation(); setLanguageMenuOpen(false); + setCountryMenuOpen(false); setPropsTypeMenuOpen(Boolean($("#propsTypeMenu")?.hidden)); }); document.querySelectorAll("#propsTypeMenu [data-value]").forEach((button) => { @@ -1269,16 +1481,24 @@ await choosePropsType(button.dataset.value); }); }); + $("#countryButton")?.addEventListener("click", (event) => { + event.stopPropagation(); + setLanguageMenuOpen(false); + setPropsTypeMenuOpen(false); + setCountryMenuOpen(Boolean($("#countryMenu")?.hidden)); + }); document.addEventListener("keydown", (event) => { if (event.key !== "Escape") return; setLanguageMenuOpen(false); setPropsTypeMenuOpen(false); + setCountryMenuOpen(false); closeModals(); }); $("#searchForm")?.addEventListener("submit", searchUser); $("#sendPropsButton")?.addEventListener("click", sendProps); $("#banButton")?.addEventListener("click", banUser); $("#unbanButton")?.addEventListener("click", unbanUser); + $("#changeCountryButton")?.addEventListener("click", changeUserCountry); } async function init() { diff --git a/h5/hyapp/manager-center/style.css b/h5/hyapp/manager-center/style.css index 1425ee2..50d8f11 100644 --- a/h5/hyapp/manager-center/style.css +++ b/h5/hyapp/manager-center/style.css @@ -538,6 +538,16 @@ textarea:focus { font-weight: 800; } +.current-country { + margin: 0 0 10px; + border-radius: 8px; + padding: 9px 10px; + background: #f1f5f9; + color: #475569; + font-size: 13px; + font-weight: 800; +} + .custom-select { position: relative; }