386 lines
16 KiB
JavaScript
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();
|