2026-04-28 20:26:42 +08:00

822 lines
28 KiB
JavaScript

(function () {
const API_BASE_URL = "https://jvapi.haiyihy.com";
const EXCHANGE_RATE = 80000;
const supportedLanguages = ["en", "ar", "tr", "id"];
const languageLabels = {
en: "EN",
ar: "AR",
tr: "TR",
id: "ID"
};
const translations = {
en: {
page_label: "Withdraw and exchange",
back: "Back",
title: "Withdraw/Exchange",
language_button_aria: "Change language",
available_income: "salary",
withdraw: "Withdraw",
exchange: "Exchange",
transfer: "Transfer",
withdraw_amount: "Withdraw amount",
exchange_amount: "Exchange amount",
transfer_amount: "Transfer amount",
transfer_to: "Transfer to",
search: "Search",
searching: "Searching...",
minimum: "Minimum 50 USD",
all: "All",
exchange_rate: "Exchange rate",
payment_method: "USDT TRC20 address",
payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.",
no_payment_info: "No USDT address",
no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.",
trc20_address: "TRC20 address",
save_address: "Save address",
saving: "Saving...",
usdt_only: "USDT only",
withdraw_now: "Withdraw now",
exchange_now: "Exchange now",
transfer_now: "Transfer now",
submitting: "Submitting...",
payee: "Payee",
uid_prefix: "UID:",
missing_token: "Missing token",
load_balance_failed: "Failed to load balance",
load_payment_failed: "Failed to load payment info",
missing_payment_info: "Please add a TRC20 USDT address",
invalid_address: "Please enter a valid TRC20 USDT address",
invalid_amount: "Please enter a valid amount",
amount_too_low: "Minimum withdrawal amount is 50 USD",
amount_exceeds_balance: "Amount exceeds salary",
payee_not_found: "User not found",
missing_payee: "Please search and select a user",
address_saved: "Address saved",
withdraw_success: "Withdrawal submitted",
withdraw_failed: "Withdrawal failed",
exchange_success: "Exchange success",
exchange_failed: "Exchange failed",
transfer_success: "Transfer success",
transfer_failed: "Transfer failed"
},
ar: {
page_label: "السحب والتبديل",
back: "رجوع",
title: "سحب/تبديل",
language_button_aria: "تغيير اللغة",
available_income: "الراتب",
withdraw: "سحب",
exchange: "تبديل",
transfer: "تحويل",
withdraw_amount: "مبلغ السحب",
exchange_amount: "مبلغ التبديل",
transfer_amount: "مبلغ التحويل",
transfer_to: "تحويل إلى",
search: "بحث",
searching: "جار البحث...",
minimum: "الحد الأدنى 50 USD",
all: "الكل",
exchange_rate: "سعر التبديل",
payment_method: "عنوان USDT TRC20",
payment_method_subtitle: "يتم إرسال السحب إلى عنوان USDT TRC20.",
no_payment_info: "لا يوجد عنوان USDT",
no_payment_info_copy: "أدخل عنوان USDT TRC20 قبل السحب.",
trc20_address: "عنوان TRC20",
save_address: "حفظ العنوان",
saving: "جار الحفظ...",
usdt_only: "USDT فقط",
withdraw_now: "اسحب الآن",
exchange_now: "بدل الآن",
transfer_now: "حوّل الآن",
submitting: "جار الإرسال...",
payee: "المستفيد",
uid_prefix: "UID:",
missing_token: "رمز الدخول مفقود",
load_balance_failed: "فشل تحميل الرصيد",
load_payment_failed: "فشل تحميل معلومات الدفع",
missing_payment_info: "يرجى إضافة عنوان USDT TRC20",
invalid_address: "يرجى إدخال عنوان USDT TRC20 صالح",
invalid_amount: "يرجى إدخال مبلغ صالح",
amount_too_low: "الحد الأدنى للسحب هو 50 USD",
amount_exceeds_balance: "المبلغ يتجاوز الراتب",
payee_not_found: "لم يتم العثور على المستخدم",
missing_payee: "يرجى البحث واختيار مستخدم",
address_saved: "تم حفظ العنوان",
withdraw_success: "تم إرسال طلب السحب",
withdraw_failed: "فشل السحب",
exchange_success: "تم التبديل",
exchange_failed: "فشل التبديل",
transfer_success: "تم التحويل",
transfer_failed: "فشل التحويل"
},
tr: {
page_label: "Çekme ve değişim",
back: "Geri",
title: "Çek/Değiştir",
language_button_aria: "Dili değiştir",
available_income: "Maaş",
withdraw: "Withdraw",
exchange: "Exchange",
transfer: "Transfer",
withdraw_amount: "Withdraw amount",
exchange_amount: "Exchange amount",
transfer_amount: "Transfer amount",
transfer_to: "Transfer to",
search: "Search",
searching: "Searching...",
minimum: "Minimum 50 USD",
all: "All",
exchange_rate: "Exchange rate",
payment_method: "USDT TRC20 address",
payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.",
no_payment_info: "No USDT address",
no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.",
trc20_address: "TRC20 address",
save_address: "Save address",
saving: "Saving...",
usdt_only: "USDT only",
withdraw_now: "Withdraw now",
exchange_now: "Exchange now",
transfer_now: "Transfer now",
submitting: "Submitting...",
payee: "Payee",
uid_prefix: "UID:",
missing_token: "Missing token",
load_balance_failed: "Failed to load balance",
load_payment_failed: "Failed to load payment info",
missing_payment_info: "Please add a TRC20 USDT address",
invalid_address: "Please enter a valid TRC20 USDT address",
invalid_amount: "Please enter a valid amount",
amount_too_low: "Minimum withdrawal amount is 50 USD",
amount_exceeds_balance: "Amount exceeds salary",
payee_not_found: "User not found",
missing_payee: "Please search and select a user",
address_saved: "Address saved",
withdraw_success: "Withdrawal submitted",
withdraw_failed: "Withdrawal failed",
exchange_success: "Exchange success",
exchange_failed: "Exchange failed",
transfer_success: "Transfer success",
transfer_failed: "Transfer failed"
},
id: {
page_label: "Tarik dan tukar",
back: "Kembali",
title: "Tarik/Tukar",
language_button_aria: "Ubah bahasa",
available_income: "Gaji",
withdraw: "Tarik",
exchange: "Tukar",
transfer: "Transfer",
withdraw_amount: "Jumlah penarikan",
exchange_amount: "Jumlah penukaran",
transfer_amount: "Jumlah transfer",
transfer_to: "Transfer ke",
search: "Cari",
searching: "Mencari...",
minimum: "Minimum 50 USD",
all: "Semua",
exchange_rate: "Kurs penukaran",
payment_method: "Alamat USDT TRC20",
payment_method_subtitle: "Penarikan dikirim ke alamat USDT TRC20 Anda.",
no_payment_info: "Tidak ada alamat USDT",
no_payment_info_copy: "Isi alamat USDT TRC20 sebelum menarik.",
trc20_address: "Alamat TRC20",
save_address: "Simpan alamat",
saving: "Menyimpan...",
usdt_only: "Hanya USDT",
withdraw_now: "Tarik sekarang",
exchange_now: "Tukar sekarang",
transfer_now: "Transfer sekarang",
submitting: "Mengirim...",
payee: "Penerima",
uid_prefix: "UID:",
missing_token: "Token tidak ditemukan",
load_balance_failed: "Gagal memuat saldo",
load_payment_failed: "Gagal memuat informasi pembayaran",
missing_payment_info: "Tambahkan alamat USDT TRC20",
invalid_address: "Masukkan alamat USDT TRC20 yang valid",
invalid_amount: "Masukkan jumlah yang valid",
amount_too_low: "Minimum penarikan adalah 50 USD",
amount_exceeds_balance: "Jumlah melebihi gaji",
payee_not_found: "Pengguna tidak ditemukan",
missing_payee: "Cari dan pilih pengguna",
address_saved: "Alamat disimpan",
withdraw_success: "Penarikan terkirim",
withdraw_failed: "Penarikan gagal",
exchange_success: "Penukaran berhasil",
exchange_failed: "Penukaran gagal",
transfer_success: "Transfer berhasil",
transfer_failed: "Transfer gagal"
}
};
const params = readURLParams();
let currentLang = normalizeLanguage(params.get("lang"));
let currentMode = normalizeMode(params.get("mode")) || "withdraw";
let currentMessages = translations[currentLang] || translations.en;
let availableBalance = 0;
let selectedPaymentInfo = null;
let selectedPayee = null;
let currentProfile = null;
let isSubmitting = false;
let isSavingAddress = false;
let isSearchingPayee = false;
const inputs = {
withdraw: document.querySelector("#withdrawAmount"),
exchange: document.querySelector("#exchangeAmount"),
transfer: document.querySelector("#transferAmount")
};
const balanceAmount = document.querySelector("#balanceAmount");
const emptyState = document.querySelector(".empty-address-state");
const addressForm = document.querySelector("#addressForm");
const trc20AddressInput = document.querySelector("#trc20Address");
const saveAddressButton = document.querySelector("#saveAddressButton");
const boundCard = document.querySelector(".bound-address-card");
const paymentSection = document.querySelector("[data-payment-section]");
const submitButton = document.querySelector(".submit-button");
const toast = document.querySelector(".toast");
const payeeResult = document.querySelector("#payeeResult");
const transferAccountInput = document.querySelector("#transferAccount");
const searchPayeeButton = document.querySelector("#searchPayeeButton");
const exchangeRateText = document.querySelector("#exchangeRateText");
function readURLParams() {
const result = new URLSearchParams(window.location.search);
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
const hashParams = new URLSearchParams(hashQuery);
hashParams.forEach((value, key) => {
if (!result.has(key)) result.set(key, value);
});
return result;
}
function readRawParam(name) {
const queries = [window.location.search.replace(/^\?/, "")];
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
for (const query of queries) {
for (const part of query.split("&")) {
if (!part) continue;
const equalIndex = part.indexOf("=");
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
let decodedKey = "";
try {
decodedKey = decodeURIComponent(rawKey);
} catch (error) {
continue;
}
if (decodedKey !== name) continue;
try {
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
} catch (error) {
return rawValue;
}
}
}
return "";
}
function normalizeLanguage(lang) {
const value = String(lang || "en").toLowerCase();
return supportedLanguages.includes(value) ? value : "en";
}
function normalizeMode(mode) {
return ["withdraw", "exchange", "transfer"].includes(mode) ? mode : "";
}
function apiBaseURL() {
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
if (override) return String(override).replace(/\/+$/, "");
return API_BASE_URL;
}
function normalizeAuthorization(token) {
const value = String(token || "").trim();
if (!value) return "";
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
}
function authHeadersFromToken(token, hasBody) {
const headers = {
Authorization: normalizeAuthorization(token),
"req-lang": currentLang,
"req-client": "H5",
"req-version": "V2",
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU"
};
if (hasBody) headers["content-type"] = "application/json";
return headers;
}
async function requestJSON(path, options = {}) {
const token = readRawParam("token");
if (!token) throw new Error(message("missing_token", "Missing token"));
const response = await fetch(`${apiBaseURL()}${path}`, {
method: options.method || "GET",
cache: "no-store",
headers: authHeadersFromToken(token, options.body !== undefined),
body: options.body === undefined ? undefined : JSON.stringify(options.body)
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`);
error.response = data;
throw error;
}
return data;
}
function message(key, fallback) {
return currentMessages[key] || translations.en[key] || fallback || key;
}
function formatMoney(value) {
const number = Number(value);
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
}
function moneyNumber(value) {
const number = Number(value);
return Number.isFinite(number) ? number : 0;
}
function activeInput() {
return inputs[currentMode] || null;
}
function activeAmount() {
return moneyNumber(activeInput()?.value);
}
function formatExchangeRate() {
return `1$ = ${EXCHANGE_RATE} golds`;
}
function isValidTrc20Address(value) {
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(String(value || "").trim());
}
function maskAddress(value) {
const text = String(value || "").trim();
if (text.length <= 12) return text || "-";
return `${text.slice(0, 6)}...${text.slice(-6)}`;
}
function applyLanguage(lang) {
currentLang = normalizeLanguage(lang);
currentMessages = translations[currentLang] || translations.en;
document.documentElement.lang = currentLang;
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
document.querySelectorAll("[data-i18n]").forEach((node) => {
const value = currentMessages[node.dataset.i18n];
if (value) node.textContent = value;
});
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
const value = currentMessages[node.dataset.i18nAria];
if (value) node.setAttribute("aria-label", value);
});
const languageButton = document.querySelector(".language-button");
if (languageButton) {
languageButton.textContent = languageLabels[currentLang];
languageButton.setAttribute("aria-label", message("language_button_aria", "Change language"));
}
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
const isActive = button.dataset.lang === currentLang;
button.classList.toggle("is-active", isActive);
button.setAttribute("aria-pressed", String(isActive));
});
if (exchangeRateText) exchangeRateText.textContent = formatExchangeRate();
renderPayee();
updateSubmitState();
}
function setMode(mode) {
const nextMode = normalizeMode(mode) || "withdraw";
currentMode = nextMode;
document.querySelectorAll(".mode-tab").forEach((tab) => {
const active = tab.dataset.mode === nextMode;
tab.classList.toggle("active", active);
tab.setAttribute("aria-selected", String(active));
});
document.querySelectorAll(".mode-panel").forEach((panel) => {
panel.classList.toggle("active", panel.dataset.panel === nextMode);
});
if (paymentSection) paymentSection.hidden = nextMode !== "withdraw";
updateSubmitState();
}
function updateSubmitState() {
if (!submitButton) return;
if (isSubmitting) {
submitButton.textContent = message("submitting", "Submitting...");
submitButton.disabled = true;
return;
}
const submitLabels = {
withdraw: message("withdraw_now", "Withdraw now"),
exchange: message("exchange_now", "Exchange now"),
transfer: message("transfer_now", "Transfer now")
};
submitButton.textContent = submitLabels[currentMode];
const amount = activeAmount();
const exceedsBalance = amount > availableBalance;
const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id;
const missingPayee = currentMode === "transfer" && !selectedPayee?.id;
submitButton.disabled = !amount ||
amount <= 0 ||
exceedsBalance ||
missingWithdrawInfo ||
missingPayee ||
(currentMode === "withdraw" && amount < 50);
}
function showToast(keyOrText, fallback) {
if (!toast) return;
toast.textContent = message(keyOrText, fallback || keyOrText);
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2200);
}
function renderBalance(value) {
availableBalance = moneyNumber(value);
if (balanceAmount) balanceAmount.textContent = formatMoney(availableBalance);
Object.values(inputs).forEach((input) => {
if (input) input.max = formatMoney(availableBalance);
});
updateSubmitState();
}
function renderPaymentInfo(paymentInfo) {
selectedPaymentInfo = paymentInfo || null;
if (!selectedPaymentInfo) {
if (emptyState) emptyState.hidden = false;
if (addressForm) addressForm.hidden = false;
if (boundCard) boundCard.hidden = true;
updateSubmitState();
return;
}
const badge = boundCard?.querySelector(".network-badge");
const network = boundCard?.querySelector(".bound-network");
const address = boundCard?.querySelector(".bound-address");
const meta = boundCard?.querySelector(".bound-meta");
if (badge) badge.textContent = "TRC20";
if (network) network.textContent = "USDT TRC20";
if (address) address.textContent = maskAddress(selectedPaymentInfo.cardNo);
if (meta) meta.textContent = message("usdt_only", "USDT only");
if (emptyState) emptyState.hidden = true;
if (addressForm) addressForm.hidden = true;
if (boundCard) boundCard.hidden = false;
updateSubmitState();
}
function renderPayee() {
if (!payeeResult) return;
if (!selectedPayee) {
payeeResult.hidden = true;
payeeResult.innerHTML = "";
updateSubmitState();
return;
}
const name = selectedPayee.userNickname || selectedPayee.name || "-";
const account = selectedPayee.ownSpecialId?.account || selectedPayee.account || selectedPayee.id || "-";
const avatar = selectedPayee.userAvatar || selectedPayee.avatar || "";
payeeResult.hidden = false;
payeeResult.innerHTML = `
<div class="payee-avatar">${avatar ? `<img src="${escapeHTML(avatar)}" alt="">` : escapeHTML(name.slice(0, 1) || "U")}</div>
<div class="payee-copy">
<div class="payee-name">${escapeHTML(name)}</div>
<div class="payee-id">${escapeHTML(message("uid_prefix", "UID:"))} ${escapeHTML(account)}</div>
</div>
`;
updateSubmitState();
}
function escapeHTML(value) {
return String(value || "").replace(/[&<>"']/g, (char) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;"
})[char]);
}
async function fetchBalance() {
try {
const data = await requestJSON("/wallet/bank/balance");
const body = data.body ?? data.data ?? 0;
renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0);
} catch (error) {
console.error("Failed to load bank balance:", error);
renderBalance(0);
showToast("load_balance_failed", error.message);
}
}
async function fetchUserProfile() {
try {
const data = await requestJSON("/team/member/profile");
currentProfile = data.body ?? data.data ?? null;
} catch (error) {
console.error("Failed to load user profile:", error);
currentProfile = null;
}
}
async function fetchPaymentInfo() {
try {
const data = await requestJSON("/wallet/bank-card/list");
const cards = Array.isArray(data.body) ? data.body : Array.isArray(data.data) ? data.data : [];
const usdtCards = cards.filter((item) => item.cardType === "USDT");
renderPaymentInfo(usdtCards.find((item) => item.use) || usdtCards[0] || null);
} catch (error) {
console.error("Failed to load payment info:", error);
renderPaymentInfo(null);
showToast("load_payment_failed", error.message);
}
}
function normalizeAmountInput(event) {
const input = event?.target || activeInput();
if (!input) return;
const raw = String(input.value || "").replace(/[^\d.]/g, "");
const parts = raw.split(".");
let nextValue = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join("")}` : raw;
const amount = Number(nextValue);
if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoney(availableBalance);
input.value = nextValue;
updateSubmitState();
}
function validateAmount(requireMinimum) {
const amount = activeAmount();
if (!amount || amount <= 0) return "invalid_amount";
if (requireMinimum && amount < 50) return "amount_too_low";
if (amount > availableBalance) return "amount_exceeds_balance";
return "";
}
async function saveAddress() {
const address = String(trc20AddressInput?.value || "").trim();
if (!isValidTrc20Address(address)) {
showToast("invalid_address");
return;
}
if (!currentProfile) await fetchUserProfile();
isSavingAddress = true;
if (saveAddressButton) {
saveAddressButton.disabled = true;
saveAddressButton.textContent = message("saving", "Saving...");
}
try {
const data = await requestJSON("/wallet/bank-card/add", {
method: "POST",
body: {
userId: currentProfile?.id || currentProfile?.userId || "",
cardType: "USDT",
cardName: "USDT",
cardNo: address,
payee: "USDT-TRC20"
}
});
const created = data.body || data.data || null;
if (created?.id) {
renderPaymentInfo({ ...created, cardType: "USDT", cardNo: address });
await requestJSON("/wallet/bank-card/use", {
method: "POST",
body: { id: created.id }
}).catch((error) => console.warn("Failed to set USDT address in use:", error));
}
await fetchPaymentInfo();
showToast("address_saved");
} catch (error) {
console.error("Failed to save TRC20 address:", error);
showToast(error.message || message("load_payment_failed", "Failed to load payment info"));
} finally {
isSavingAddress = false;
if (saveAddressButton) {
saveAddressButton.disabled = false;
saveAddressButton.textContent = message("save_address", "Save address");
}
}
}
async function searchPayee() {
const account = String(transferAccountInput?.value || "").trim();
if (!account) {
showToast("missing_payee");
return;
}
selectedPayee = null;
renderPayee();
isSearchingPayee = true;
if (searchPayeeButton) {
searchPayeeButton.disabled = true;
searchPayeeButton.textContent = message("searching", "Searching...");
}
try {
const path = `/wallet/bank/search/user-profile?account=${encodeURIComponent(account)}&type=AGENT&searchType=TRANSFER`;
const data = await requestJSON(path);
const body = data.body ?? data.data ?? null;
if (!body?.id) {
showToast("payee_not_found");
return;
}
selectedPayee = body;
renderPayee();
} catch (error) {
console.error("Failed to search payee:", error);
showToast(error.message || message("payee_not_found", "User not found"));
} finally {
isSearchingPayee = false;
if (searchPayeeButton) {
searchPayeeButton.disabled = false;
searchPayeeButton.textContent = message("search", "Search");
}
}
}
function validateWithdraw() {
if (!selectedPaymentInfo?.id) return "missing_payment_info";
return validateAmount(true);
}
function validateTransfer() {
if (!selectedPayee?.id) return "missing_payee";
return validateAmount(false);
}
async function submitWithdraw() {
const errorKey = validateWithdraw();
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON("/wallet/bank/withdraw", {
method: "POST",
body: {
amount: activeAmount(),
acceptBankCardId: selectedPaymentInfo.id,
salaryType: "HOST_SALARY"
}
});
showToast("withdraw_success");
}
async function submitExchange() {
const errorKey = validateAmount(false);
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON("/wallet/bank/exchange/gold", {
method: "POST",
body: {
amount: activeAmount()
}
});
showToast("exchange_success");
}
async function submitTransfer() {
const errorKey = validateTransfer();
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON("/wallet/bank/transferGold", {
method: "POST",
body: {
amount: activeAmount(),
acceptUserId: selectedPayee.id,
acceptMethod: "BANK_TRANSFER"
}
});
showToast("transfer_success");
}
async function submitCurrentMode() {
if (isSubmitting) return;
isSubmitting = true;
updateSubmitState();
try {
if (currentMode === "withdraw") await submitWithdraw();
if (currentMode === "exchange") await submitExchange();
if (currentMode === "transfer") await submitTransfer();
const input = activeInput();
if (input) input.value = "";
await fetchBalance();
} catch (error) {
console.error(`Failed to submit ${currentMode}:`, error);
const fallback = {
withdraw: "withdraw_failed",
exchange: "exchange_failed",
transfer: "transfer_failed"
}[currentMode];
showToast(error.message || message(fallback, "Submit failed"));
} finally {
isSubmitting = false;
updateSubmitState();
}
}
function syncBackFallback() {
document.querySelector(".back-button")?.addEventListener("click", () => {
if (window.history.length > 1) {
window.history.back();
return;
}
const nextParams = new URLSearchParams(params);
const token = readRawParam("token");
if (token) nextParams.set("token", token);
if (currentLang) nextParams.set("lang", currentLang);
const query = nextParams.toString();
window.location.href = `../host-center/index.html${query ? `?${query}` : ""}`;
});
}
function setLanguageMenuOpen(open) {
const languageButton = document.querySelector(".language-button");
const languageMenu = document.querySelector(".language-menu");
if (!languageButton || !languageMenu) return;
languageMenu.hidden = !open;
languageButton.setAttribute("aria-expanded", String(open));
}
document.querySelector(".language-button")?.addEventListener("click", (event) => {
event.stopPropagation();
const languageMenu = document.querySelector(".language-menu");
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
});
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
button.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
applyLanguage(button.dataset.lang);
});
});
document.addEventListener("click", () => {
setLanguageMenuOpen(false);
});
document.querySelectorAll(".mode-tab").forEach((tab) => {
tab.addEventListener("click", () => setMode(tab.dataset.mode));
});
document.querySelector(".text-action")?.addEventListener("click", () => {
if (inputs.withdraw) inputs.withdraw.value = formatMoney(availableBalance);
updateSubmitState();
});
Object.values(inputs).forEach((input) => input?.addEventListener("input", normalizeAmountInput));
transferAccountInput?.addEventListener("input", () => {
selectedPayee = null;
renderPayee();
});
transferAccountInput?.addEventListener("keydown", (event) => {
if (event.key === "Enter") searchPayee();
});
searchPayeeButton?.addEventListener("click", searchPayee);
saveAddressButton?.addEventListener("click", saveAddress);
submitButton?.addEventListener("click", submitCurrentMode);
syncBackFallback();
applyLanguage(currentLang);
setMode(currentMode);
fetchUserProfile();
fetchBalance();
fetchPaymentInfo();
})();