chatapp-h5/h5/available-income/available-income.js
2026-04-24 21:17:05 +08:00

386 lines
16 KiB
JavaScript

import { applyLang, bindHeader, connectToApp, createMask, get, money, navigateTo, post, safeJsonArray, t, toast } from "../bd-static/shared.js";
const query = new URLSearchParams(window.location.search);
const state = {
identity: readIdentity(),
activeAction: query.get("activeAction") || "Exchange",
salaryTab: 0,
balances: { ADMIN_SALARY: {}, BD_SALARY: {}, BD_LEADER_SALARY: {}, TOTAL: {} },
payee: readPayee(),
kyc: null,
bankCard: null,
transferId: null,
exchangeId: null,
cashAmount: null
};
const transfers = [
{ id: 1, amount: 10500, price: 1 },
{ id: 2, amount: 52500, price: 5 },
{ id: 3, amount: 105000, price: 10 },
{ id: 4, amount: 525000, price: 50 },
{ id: 5, amount: 1050000, price: 100 },
{ id: 6, amount: 2100000, price: 200 }
];
const exchanges = transfers.map((item) => ({ id: item.id, coins: item.amount, cash: item.price }));
const actions = [
{ name: "Exchange", lang: "exchange" },
{ name: "Transfer", lang: "transfer" },
{ name: "Cash out", lang: "cash_out" }
];
bindHeader("available_salaries");
applyLang();
document.getElementById("detailsButton").addEventListener("click", () => navigateTo("/income-details"));
async function init() {
try {
await connectToApp();
await Promise.all([loadBalances(), loadKyc(), loadCards()]);
} catch (error) {
toast(error.response?.errorMsg || error.message || t("failed_to_load_balance"));
} finally {
render();
}
}
function readIdentity() {
try {
const data = JSON.parse(localStorage.getItem("identity") || "{}");
return data.identity || data.primaryRole || localStorage.getItem("userIdentity") || "BD";
} catch {
return "BD";
}
}
function readPayee() {
for (const storage of [sessionStorage, localStorage]) {
try {
const value = storage.getItem("transfer.payee") || storage.getItem("payee");
if (value) return JSON.parse(value);
} catch {}
}
return { id: null, account: "", name: "", avatar: "", type: "", isHost: false, isAgency: false, hasSalary: false };
}
async function loadBalances() {
if (state.identity === "ADMIN") {
state.balances.ADMIN_SALARY = await balance("ADMIN_SALARY");
} else if (state.identity === "BD_LEADER") {
const [total, leader, bd] = await Promise.all([get("/wallet/salary-account/balance/total"), balance("BD_LEADER_SALARY"), balance("BD_SALARY")]);
state.balances.TOTAL = total.body || {};
state.balances.BD_LEADER_SALARY = leader;
state.balances.BD_SALARY = bd;
} else {
state.balances.BD_SALARY = await balance("BD_SALARY");
}
}
async function balance(type) {
const response = await get(`/wallet/salary-account/balance?salaryType=${type}`);
return response.status && response.body ? response.body : {};
}
async function loadKyc() {
const response = await get("/wallet/withdraw-info/list");
const body = response.status && Array.isArray(response.body) ? response.body : [];
state.kyc = body.length > 0 ? { ...body[0], previewUrls: safeJsonArray(body[0].passportFrontUrl) } : null;
}
async function loadCards() {
const response = await get("/wallet/bank-card/list");
const body = response.status && Array.isArray(response.body) ? response.body : [];
state.bankCard = body.find((item) => item.use) || null;
}
function salaryType() {
if (state.identity === "ADMIN") return "ADMIN_SALARY";
if (state.identity === "BD_LEADER") return state.salaryTab === 0 ? "BD_SALARY" : "BD_LEADER_SALARY";
return "BD_SALARY";
}
function visibleBalance() {
if (state.identity === "BD_LEADER") return state.balances.TOTAL.availableBalance || 0;
return state.balances[salaryType()]?.availableBalance || 0;
}
function operatingBalance() {
return state.balances[salaryType()]?.availableBalance || 0;
}
function render() {
document.getElementById("totalBalance").textContent = `$${money(visibleBalance())}`;
renderIdentitySwitch();
renderActions();
renderPanel();
}
function renderIdentitySwitch() {
const holder = document.getElementById("identitySwitch");
if (state.identity !== "BD_LEADER") {
holder.hidden = true;
return;
}
holder.hidden = false;
const rows = [
{ label: t("bd"), detail: t("my_bd_salary"), salary: state.balances.BD_SALARY.availableBalance || 0 },
{ label: t("bd_leader"), detail: t("my_bd_leader_salary"), salary: state.balances.BD_LEADER_SALARY.availableBalance || 0 }
];
holder.innerHTML = `
<div class="row" style="gap:8px;margin-bottom:10px">
${rows.map((row, index) => `<button class="action-tab ${state.salaryTab === index ? "active" : ""}" data-salary="${index}" type="button">${row.label}</button>`).join("")}
</div>
<div><b>${rows[state.salaryTab].detail}</b></div>
<div style="font-size:1.35em;font-weight:700">$${money(rows[state.salaryTab].salary)}</div>
`;
holder.querySelectorAll("[data-salary]").forEach((button) => {
button.addEventListener("click", () => {
state.salaryTab = Number(button.dataset.salary);
state.transferId = null;
state.exchangeId = null;
render();
});
});
}
function renderActions() {
const nav = document.getElementById("actionTabs");
nav.innerHTML = actions.map((item) => `<button class="action-tab ${state.activeAction === item.name ? "active" : ""}" data-action-name="${item.name}" type="button">${t(item.lang)}</button>`).join("");
nav.querySelectorAll("[data-action-name]").forEach((button) => {
button.addEventListener("click", () => {
state.activeAction = button.dataset.actionName;
state.transferId = null;
state.exchangeId = null;
render();
});
});
}
function renderPanel() {
if (state.activeAction === "Transfer") renderTransfer();
else if (state.activeAction === "Cash out") renderCashOut();
else renderExchange();
}
function renderTransfer() {
const panel = document.getElementById("actionPanel");
panel.innerHTML = `
<div class="amount-grid">
${transfers.map((item) => `<button class="amount-card ${state.transferId === item.id ? "active" : ""}" data-transfer="${item.id}" type="button">$${item.price}<br><span class="subtle">${item.amount}</span></button>`).join("")}
</div>
<button class="menu-link" id="payeeButton" type="button">
${state.payee?.id ? payeeHtml(state.payee) : `<span>${t("please_select_payee")}</span>`}
<img class="arrow" src="../bd-static/icons/arrow.svg" alt="">
</button>
<div style="display:flex;justify-content:center"><button class="primary-btn" id="transferButton" type="button">${t("transfer")}</button></div>
`;
panel.querySelectorAll("[data-transfer]").forEach((button) => button.addEventListener("click", () => {
const id = Number(button.dataset.transfer);
state.transferId = state.transferId === id ? null : id;
renderTransfer();
}));
document.getElementById("payeeButton").addEventListener("click", () => navigateTo("/search-payee?from=/available-income&activeAction=Transfer"));
const transferButton = document.getElementById("transferButton");
transferButton.disabled = !(state.transferId && state.payee?.id);
transferButton.addEventListener("click", submitTransfer);
}
function payeeHtml(payee) {
return `
<span class="payee">
<img class="avatar" src="${payee.avatar || ""}" alt="">
<span class="member-main">
<span class="member-name">${payee.name || "-"}</span>
<span class="member-meta">${t("user_id_prefix")}${payee.goodID || payee.account || "-"}</span>
</span>
</span>
`;
}
function renderExchange() {
const panel = document.getElementById("actionPanel");
panel.innerHTML = `
<div class="amount-grid">
${exchanges.map((item) => `<button class="amount-card ${state.exchangeId === item.id ? "active" : ""}" data-exchange="${item.id}" type="button">$${item.cash}<br><span class="subtle">${item.coins}</span></button>`).join("")}
</div>
<div style="display:flex;justify-content:center;margin-top:12px"><button class="primary-btn" id="exchangeButton" type="button" ${state.exchangeId ? "" : "disabled"}>${t("exchange")}</button></div>
`;
panel.querySelectorAll("[data-exchange]").forEach((button) => button.addEventListener("click", () => {
const id = Number(button.dataset.exchange);
state.exchangeId = state.exchangeId === id ? null : id;
renderExchange();
}));
document.getElementById("exchangeButton").addEventListener("click", confirmExchange);
}
function renderCashOut() {
const panel = document.getElementById("actionPanel");
const status = state.kyc?.status;
panel.innerHTML = `
<div style="display:flex;align-items:flex-end;margin-bottom:10px">
<b>${t("kyc")}</b>${state.kyc ? `: ${statusLabel(status)}` : ""}
</div>
<article class="card card-pad">
${state.kyc ? kycHtml(state.kyc) : `<button class="menu-link" id="completeKyc" type="button"><span>${t("complete_information")}</span><img class="arrow" src="../bd-static/icons/arrow.svg" alt=""></button>`}
</article>
<div style="display:flex;justify-content:center;margin-top:12px">
<button class="primary-btn" id="cashButton" type="button" style="${status === "PASS" ? "" : "background:#ccc;opacity:.6"}">${t("cash_out")}</button>
</div>
`;
const completeKyc = document.getElementById("completeKyc");
if (completeKyc) completeKyc.addEventListener("click", () => navigateTo("/bank-cards?from=/available-income&activeAction=Cash out"));
const editKyc = document.getElementById("editKyc");
if (editKyc) editKyc.addEventListener("click", () => navigateTo("/bank-cards?from=/available-income&activeAction=Cash out"));
document.getElementById("cashButton").addEventListener("click", () => {
if (state.kyc && state.kyc.status === "PASS") openCashSheet();
else if (!state.kyc) openImproveDialog();
});
}
function statusLabel(status) {
if (status === "PASS") return `<span class="badge pass">${t("review_passed")}</span>`;
if (status === "NOT_PASS") return `<span class="badge fail">${t("review_failed")}</span>`;
if (status === "PENDING") return `<span class="badge wait">${t("awaiting_review")}</span>`;
return "";
}
function kycHtml(item) {
return `
<div style="display:flex;flex-direction:column;gap:8px">
<div class="row"><b>${t("passport_id_card_title")}:</b><button type="button" id="editKyc"><img class="arrow" src="../bd-static/icons/arrow.svg" alt=""></button></div>
<div style="display:flex;gap:8px;flex-wrap:wrap">${(item.previewUrls || []).map((src) => `<img src="${src}" alt="" style="width:15vw;aspect-ratio:1/1;object-fit:cover;border-radius:10px">`).join("")}</div>
<div><b>${t("contact_number")}:</b></div><div class="subtle">${item.contactNumber || ""}</div>
<div><b>${t("other_description")}:</b></div><div class="subtle">${item.otherDescription || ""}</div>
</div>
`;
}
async function submitTransfer() {
const item = transfers.find((entry) => entry.id === state.transferId);
if (!item) return toast(t("please_select_amount"));
if (!state.payee?.id) return toast(t("please_select_payee"));
try {
const response = await post("/wallet/salary-account/transferGold", { amount: item.price, acceptUserId: state.payee.id, salaryType: salaryType() });
if (response.status) {
toast(t("transfer_dollar_success"));
state.transferId = null;
await loadBalances();
render();
}
} catch (error) {
toast(error.response?.errorMsg || t("transfer_error"));
}
}
function confirmExchange() {
if (!state.exchangeId) return toast(t("please_select_coin_amount"));
const panel = document.createElement("section");
panel.className = "card card-pad";
panel.style.cssText = "width:80%;margin:32vh auto 0;text-align:center";
panel.innerHTML = `
<div style="font-size:1.3em;font-weight:700">${t("tips")}</div>
<div class="subtle" style="margin:8px 0 16px">${t("confirm_exchange")}</div>
<div class="row">
<button class="soft-btn" id="cancelExchange" type="button">${t("cancel")}</button>
<button class="primary-btn" id="confirmExchange" type="button">${t("confirm")}</button>
</div>
`;
const mask = createMask(panel);
panel.querySelector("#cancelExchange").addEventListener("click", () => mask.remove());
panel.querySelector("#confirmExchange").addEventListener("click", () => submitExchange(mask));
}
async function submitExchange(mask) {
const item = exchanges.find((entry) => entry.id === state.exchangeId);
try {
const response = await post("/wallet/salary-account/exchange/gold", { amount: item.cash, salaryType: salaryType() });
if (response.status === true || response.errorCode === 0) {
mask.remove();
toast(t("exchange_success"));
state.exchangeId = null;
await loadBalances();
render();
}
} catch (error) {
mask.remove();
toast(error.response?.errorMsg || t("exchange_error_occurred"));
}
}
function openImproveDialog() {
const panel = document.createElement("section");
panel.className = "card card-pad";
panel.style.cssText = "width:80%;margin:30vh auto 0;text-align:center";
panel.innerHTML = `
<div style="font-size:1.3em;font-weight:700">${t("prompt")}</div>
<div style="font-weight:700;margin-top:8px">${t("fill_info_tips")}</div>
<div class="subtle">${t("card_issue_tips")}</div>
<button class="primary-btn" id="improveButton" type="button" style="margin-top:12px">${t("to_improve")}</button>
`;
createMask(panel);
panel.querySelector("#improveButton").addEventListener("click", () => navigateTo("/bank-cards?from=/available-income&activeAction=Cash out"));
}
function openCashSheet() {
const panel = document.createElement("section");
panel.className = "sheet";
panel.innerHTML = `
<div class="sheet-title">${t("cash_out")}</div>
<div class="card card-pad">
<div><b>${t("withdrawal_method")}:</b> ${state.bankCard?.cardType || ""}</div>
${state.bankCard ? `<div><b>${t("card_number")}:</b> ${state.bankCard.cardNo || ""}</div>` : ""}
<button class="menu-link" id="changeCard" type="button">
<span>${state.bankCard ? t("change_withdrawal_method") : t("please_select_bank_card")}</span>
<img class="arrow" src="../bd-static/icons/arrow.svg" alt="">
</button>
</div>
<div class="input-box" style="margin-top:12px"><input class="field" id="cashInput" inputmode="decimal" dir="ltr" placeholder="${t("enter_cash_out_amount")}"></div>
<div class="subtle" style="margin-top:8px">${t("cash_out_minimum")}</div>
<div style="display:flex;justify-content:center"><button class="primary-btn" id="submitCash" type="button" style="margin-top:12px;width:80%">${t("cash_out")}</button></div>
`;
const mask = createMask(panel);
const input = panel.querySelector("#cashInput");
input.addEventListener("input", () => {
let value = input.value.replace(/[^\d.]/g, "");
const parts = value.split(".");
if (parts.length > 2) value = `${parts[0]}.${parts.slice(1).join("")}`;
if (Number(value) > operatingBalance()) value = String(operatingBalance());
input.value = value;
state.cashAmount = Number(value) || null;
});
panel.querySelector("#changeCard").addEventListener("click", () => navigateTo("/bank-cards?from=/available-income&activeAction=Cash out"));
panel.querySelector("#submitCash").addEventListener("click", () => submitCash(mask));
}
async function submitCash(mask) {
if (!(state.kyc?.status === "PASS" && state.bankCard?.id && state.cashAmount >= 50)) {
if (!state.bankCard?.id && state.cashAmount >= 50) {
mask.remove();
openImproveDialog();
}
return;
}
try {
const response = await post("/wallet/bank/withdraw", { amount: state.cashAmount, acceptBankCardId: state.bankCard.id, salaryType: salaryType() });
if (response.status) {
mask.remove();
await loadBalances();
openSuccessDialog();
render();
}
} catch (error) {
mask.remove();
toast(t("something_went_wrong"));
}
}
function openSuccessDialog() {
const panel = document.createElement("section");
panel.className = "card card-pad";
panel.style.cssText = "width:80%;margin:34vh auto 0;text-align:center";
panel.innerHTML = `<div style="font-size:1.3em;font-weight:700">${t("tips")}</div><div style="font-weight:700;margin-top:8px">${t("cash_withdrawal_tips")}</div>`;
createMask(panel);
}
init();