(function () { const API_BASE_URL = "https://jvapi.haiyihy.com"; const EXCHANGE_RATE = 80000; const WITHDRAW_MIN_AMOUNT = 50; 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", bd_salary: "BD salary", bd_leader_salary: "BD Leader 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: "الراتب", bd_salary: "راتب BD", bd_leader_salary: "راتب قائد BD", 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ş", bd_salary: "BD maaşı", bd_leader_salary: "BD Leader 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", bd_salary: "Gaji BD", bd_leader_salary: "Gaji BD Leader", 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"; const pageIdentity = normalizeIdentity(params.get("identity")); let currentSalaryType = normalizeSalaryType(params.get("salaryType")) || defaultSalaryTypeForIdentity(pageIdentity); let currentMessages = translations[currentLang] || translations.en; let availableBalance = 0; let salaryBalances = {}; 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"); const salarySelector = document.querySelector("#salarySelector"); 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 splitHeaderPairs(value) { const result = {}; String(value || "").split(";").forEach((item) => { const index = item.indexOf("="); if (index > -1) result[item.slice(0, index).trim()] = item.slice(index + 1).trim(); }); return result; } function firstRawParam(names) { for (const name of names) { const value = readRawParam(name); if (value) return value; } return ""; } function readRequestSysOrigin() { const packed = splitHeaderPairs(firstRawParam(["req-sys-origin", "Req-Sys-Origin", "reqSysOrigin", "ReqSysOrigin"])); const origin = String(firstRawParam(["sysOrigin", "origin", "Sys-Origin"]) || packed.origin || "LIKEI") .trim() .toUpperCase(); const originChild = String( firstRawParam(["sysOriginChild", "originChild", "child", "Sys-Origin-Child"]) || packed.originChild || packed.child || origin ).trim().toUpperCase(); return { origin, originChild }; } 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 normalizeIdentity(identity) { const value = String(identity || "").trim().toUpperCase(); return ["HOST", "BD", "BD_LEADER", "ADMIN"].includes(value) ? value : ""; } function normalizeSalaryType(type) { const value = String(type || "").trim().toUpperCase(); return ["HOST_SALARY", "BD_SALARY", "BD_LEADER_SALARY", "ADMIN_SALARY"].includes(value) ? value : ""; } function defaultSalaryTypeForIdentity(identity) { if (identity === "BD_LEADER") return "BD_LEADER_SALARY"; if (identity === "BD") return "BD_SALARY"; if (identity === "ADMIN") return "ADMIN_SALARY"; return ""; } function isSalaryAccountMode() { return Boolean(currentSalaryType && currentSalaryType !== "HOST_SALARY"); } function activeSalaryType() { return currentSalaryType || "HOST_SALARY"; } function responseBody(data) { return data?.body ?? data?.data ?? null; } 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 sysOrigin = readRequestSysOrigin(); 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=${sysOrigin.origin};originChild=${sysOrigin.originChild}` }; 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 salaryBalanceValue(type) { const balance = salaryBalances[type] || {}; return moneyNumber(balance.availableBalance ?? balance.balance ?? balance.currentSalary ?? 0); } function renderSalarySelector() { if (!salarySelector) return; const showSelector = pageIdentity === "BD_LEADER"; salarySelector.hidden = !showSelector; if (!showSelector) return; salarySelector.querySelectorAll("[data-salary-type]").forEach((button) => { const type = normalizeSalaryType(button.dataset.salaryType); const active = type === currentSalaryType; const label = message(type === "BD_SALARY" ? "bd_salary" : "bd_leader_salary"); button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); button.textContent = `${label} $${formatMoney(salaryBalanceValue(type))}`; }); } function setActiveSalaryType(type) { const nextType = normalizeSalaryType(type); if (!nextType || nextType === currentSalaryType) return; currentSalaryType = nextType; renderBalance(salaryBalanceValue(nextType)); } 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(); renderSalarySelector(); 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 < WITHDRAW_MIN_AMOUNT); } 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); renderSalarySelector(); 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 = `
${avatar ? `` : escapeHTML(name.slice(0, 1) || "U")}
${escapeHTML(name)}
${escapeHTML(message("uid_prefix", "UID:"))} ${escapeHTML(account)}
`; updateSubmitState(); } function escapeHTML(value) { return String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } async function fetchBalance() { try { if (pageIdentity === "BD_LEADER") { const salaryTypes = ["BD_SALARY", "BD_LEADER_SALARY"]; const results = await Promise.allSettled( salaryTypes.map((type) => requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(type)}`)) ); salaryBalances = {}; results.forEach((result, index) => { const type = salaryTypes[index]; salaryBalances[type] = result.status === "fulfilled" ? responseBody(result.value) || {} : {}; if (result.status === "rejected") console.warn(`Failed to load ${type} balance:`, result.reason); }); if (results.every((result) => result.status === "rejected")) throw results[0].reason; if (!currentSalaryType) currentSalaryType = "BD_LEADER_SALARY"; renderBalance(salaryBalanceValue(currentSalaryType)); return; } if (isSalaryAccountMode()) { const data = await requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(currentSalaryType)}`); const body = responseBody(data) || {}; salaryBalances = { [currentSalaryType]: body }; renderBalance(body.availableBalance ?? body.balance ?? body.currentSalary ?? 0); return; } const data = await requestJSON("/wallet/bank/balance"); const body = responseBody(data) ?? 0; renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0); } catch (error) { console.error("Failed to load 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 < WITHDRAW_MIN_AMOUNT) 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: activeSalaryType() } }); showToast("withdraw_success"); } async function submitExchange() { const errorKey = validateAmount(false); if (errorKey) { showToast(errorKey); return; } await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold", { method: "POST", body: isSalaryAccountMode() ? { amount: activeAmount(), salaryType: activeSalaryType() } : { amount: activeAmount() } }); showToast("exchange_success"); } async function submitTransfer() { const errorKey = validateTransfer(); if (errorKey) { showToast(errorKey); return; } await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold", { method: "POST", body: isSalaryAccountMode() ? { amount: activeAmount(), acceptUserId: selectedPayee.id, salaryType: activeSalaryType() } : { 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(); const fallbackPage = pageIdentity === "BD_LEADER" ? "../bd-leader-center/index.html" : "../host-center/index.html"; window.location.href = `${fallbackPage}${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.querySelectorAll("[data-salary-type]").forEach((button) => { button.addEventListener("click", () => setActiveSalaryType(button.dataset.salaryType)); }); 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(); })();