feat: route bd leader salary to wallet actions
This commit is contained in:
parent
850b340600
commit
e9bb86cb94
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>BD Leader Center</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2145" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2215" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bd-leader-center" aria-label="BD Leader Center" data-i18n-aria="page_label" data-loading="true">
|
||||
@ -53,10 +53,10 @@
|
||||
<section class="card salary-card">
|
||||
<div class="salary-head">
|
||||
<div>
|
||||
<div class="salary-label" data-i18n="my_available_salary">My available salary</div>
|
||||
<div class="salary-label" data-i18n="my_available_salary">salary</div>
|
||||
<div class="salary-period" id="billTitle">-</div>
|
||||
</div>
|
||||
<button class="text-link" type="button" data-route="/available-income" data-i18n="details">Details</button>
|
||||
<button class="text-link" type="button" data-route="/hyapp/withdraw-exchange/index.html?identity=BD_LEADER&salaryType=BD_LEADER_SALARY" data-i18n="details">Withdraw/Exchange</button>
|
||||
</div>
|
||||
<div class="salary-value">
|
||||
<span aria-hidden="true">$</span>
|
||||
@ -223,6 +223,6 @@
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260428-2145" defer></script>
|
||||
<script src="./script.js?v=20260428-2215" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
language_button_aria: "Change language",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "My available salary",
|
||||
details: "Details",
|
||||
my_available_salary: "salary",
|
||||
details: "Withdraw/Exchange",
|
||||
bd_number: "BD number",
|
||||
team_salary: "Team salary",
|
||||
bd_policy_link: "BD policy",
|
||||
@ -67,8 +67,8 @@
|
||||
language_button_aria: "تغيير اللغة",
|
||||
role: "قائد BD",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "راتبي المتاح",
|
||||
details: "التفاصيل",
|
||||
my_available_salary: "الراتب",
|
||||
details: "سحب/تبديل",
|
||||
bd_number: "عدد BD",
|
||||
team_salary: "راتب الفريق",
|
||||
bd_policy_link: "سياسة BD",
|
||||
@ -117,8 +117,8 @@
|
||||
language_button_aria: "Dili değiştir",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "Kullanılabilir maaşım",
|
||||
details: "Detaylar",
|
||||
my_available_salary: "Maaş",
|
||||
details: "Withdraw/Exchange",
|
||||
bd_number: "BD sayısı",
|
||||
team_salary: "Takım maaşı",
|
||||
bd_policy_link: "BD politikası",
|
||||
@ -167,8 +167,8 @@
|
||||
language_button_aria: "Ubah bahasa",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "Gaji tersedia",
|
||||
details: "Detail",
|
||||
my_available_salary: "Gaji",
|
||||
details: "Withdraw/Exchange",
|
||||
bd_number: "Jumlah BD",
|
||||
team_salary: "Gaji tim",
|
||||
bd_policy_link: "Kebijakan BD",
|
||||
@ -451,6 +451,11 @@
|
||||
if (node) node.textContent = formatMoney(balance);
|
||||
}
|
||||
|
||||
function balanceAmount(balance) {
|
||||
const value = Number(balance?.availableBalance ?? balance?.balance ?? balance?.currentSalary ?? 0);
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
|
||||
function renderBill() {
|
||||
const bill = state.bill || {};
|
||||
const members = Array.isArray(bill.memberBillList) ? bill.memberBillList : [];
|
||||
@ -1019,7 +1024,24 @@
|
||||
}
|
||||
|
||||
async function fetchBalanceTotal() {
|
||||
state.balanceTotal = await requestJSON("/wallet/salary-account/balance/total") || {};
|
||||
const salaryTypes = ["BD_SALARY", "BD_LEADER_SALARY"];
|
||||
const results = await Promise.allSettled(
|
||||
salaryTypes.map((salaryType) => requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(salaryType)}`))
|
||||
);
|
||||
const balances = {};
|
||||
results.forEach((result, index) => {
|
||||
const salaryType = salaryTypes[index];
|
||||
if (result.status === "fulfilled") {
|
||||
balances[salaryType] = result.value || {};
|
||||
} else {
|
||||
balances[salaryType] = {};
|
||||
console.warn(`Failed to load ${salaryType} balance:`, result.reason);
|
||||
}
|
||||
});
|
||||
state.balanceTotal = {
|
||||
...balances,
|
||||
availableBalance: salaryTypes.reduce((total, salaryType) => total + balanceAmount(balances[salaryType]), 0)
|
||||
};
|
||||
renderBalance();
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>Withdraw/Exchange</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2030" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2215" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="wallet-page" aria-label="Withdraw and exchange" data-i18n-aria="page_label">
|
||||
@ -46,6 +46,13 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="salary-selector-card" id="salarySelector" hidden>
|
||||
<div class="salary-selector-tabs">
|
||||
<button class="salary-selector-tab" type="button" data-salary-type="BD_SALARY" data-i18n="bd_salary">BD salary</button>
|
||||
<button class="salary-selector-tab" type="button" data-salary-type="BD_LEADER_SALARY" data-i18n="bd_leader_salary">BD Leader salary</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mode-card">
|
||||
<div class="mode-tabs" role="tablist" aria-label="Withdraw, exchange, or transfer">
|
||||
<button class="mode-tab active" type="button" role="tab" aria-selected="true" data-mode="withdraw" data-i18n="withdraw">Withdraw</button>
|
||||
@ -147,6 +154,6 @@
|
||||
<div class="toast" hidden></div>
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260428-2030" defer></script>
|
||||
<script src="./script.js?v=20260428-2215" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
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",
|
||||
@ -66,6 +68,8 @@
|
||||
title: "سحب/تبديل",
|
||||
language_button_aria: "تغيير اللغة",
|
||||
available_income: "الراتب",
|
||||
bd_salary: "راتب BD",
|
||||
bd_leader_salary: "راتب قائد BD",
|
||||
withdraw: "سحب",
|
||||
exchange: "تبديل",
|
||||
transfer: "تحويل",
|
||||
@ -116,6 +120,8 @@
|
||||
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",
|
||||
@ -166,6 +172,8 @@
|
||||
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",
|
||||
@ -215,8 +223,11 @@
|
||||
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;
|
||||
@ -242,6 +253,7 @@
|
||||
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);
|
||||
@ -289,6 +301,35 @@
|
||||
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(/\/+$/, "");
|
||||
@ -360,6 +401,34 @@
|
||||
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());
|
||||
}
|
||||
@ -396,6 +465,7 @@
|
||||
button.setAttribute("aria-pressed", String(isActive));
|
||||
});
|
||||
if (exchangeRateText) exchangeRateText.textContent = formatExchangeRate();
|
||||
renderSalarySelector();
|
||||
renderPayee();
|
||||
updateSubmitState();
|
||||
}
|
||||
@ -456,6 +526,7 @@
|
||||
function renderBalance(value) {
|
||||
availableBalance = moneyNumber(value);
|
||||
if (balanceAmount) balanceAmount.textContent = formatMoney(availableBalance);
|
||||
renderSalarySelector();
|
||||
Object.values(inputs).forEach((input) => {
|
||||
if (input) input.max = formatMoney(availableBalance);
|
||||
});
|
||||
@ -524,11 +595,36 @@
|
||||
|
||||
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 = data.body ?? data.data ?? 0;
|
||||
const body = responseBody(data) ?? 0;
|
||||
renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0);
|
||||
} catch (error) {
|
||||
console.error("Failed to load bank balance:", error);
|
||||
console.error("Failed to load balance:", error);
|
||||
renderBalance(0);
|
||||
showToast("load_balance_failed", error.message);
|
||||
}
|
||||
@ -683,7 +779,7 @@
|
||||
body: {
|
||||
amount: activeAmount(),
|
||||
acceptBankCardId: selectedPaymentInfo.id,
|
||||
salaryType: "HOST_SALARY"
|
||||
salaryType: activeSalaryType()
|
||||
}
|
||||
});
|
||||
showToast("withdraw_success");
|
||||
@ -696,11 +792,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
await requestJSON("/wallet/bank/exchange/gold", {
|
||||
await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold", {
|
||||
method: "POST",
|
||||
body: {
|
||||
amount: activeAmount()
|
||||
}
|
||||
body: isSalaryAccountMode()
|
||||
? { amount: activeAmount(), salaryType: activeSalaryType() }
|
||||
: { amount: activeAmount() }
|
||||
});
|
||||
showToast("exchange_success");
|
||||
}
|
||||
@ -712,13 +808,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
await requestJSON("/wallet/bank/transferGold", {
|
||||
await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold", {
|
||||
method: "POST",
|
||||
body: {
|
||||
amount: activeAmount(),
|
||||
acceptUserId: selectedPayee.id,
|
||||
acceptMethod: "BANK_TRANSFER"
|
||||
}
|
||||
body: isSalaryAccountMode()
|
||||
? { amount: activeAmount(), acceptUserId: selectedPayee.id, salaryType: activeSalaryType() }
|
||||
: { amount: activeAmount(), acceptUserId: selectedPayee.id, acceptMethod: "BANK_TRANSFER" }
|
||||
});
|
||||
showToast("transfer_success");
|
||||
}
|
||||
@ -760,7 +854,8 @@
|
||||
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}` : ""}`;
|
||||
const fallbackPage = pageIdentity === "BD_LEADER" ? "../bd-leader-center/index.html" : "../host-center/index.html";
|
||||
window.location.href = `${fallbackPage}${query ? `?${query}` : ""}`;
|
||||
});
|
||||
}
|
||||
|
||||
@ -795,6 +890,10 @@
|
||||
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();
|
||||
|
||||
@ -294,6 +294,7 @@ select {
|
||||
}
|
||||
|
||||
.balance-card,
|
||||
.salary-selector-card,
|
||||
.mode-card,
|
||||
.card-section,
|
||||
.bind-panel {
|
||||
@ -332,6 +333,32 @@ select {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.salary-selector-card {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.salary-selector-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.salary-selector-tab {
|
||||
min-height: 40px;
|
||||
padding: 0 8px;
|
||||
border-radius: 8px;
|
||||
background: #eef1f1;
|
||||
color: #777a80;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.salary-selector-tab.active {
|
||||
background: #e7fbf8;
|
||||
color: #0e8f82;
|
||||
box-shadow: inset 0 0 0 1px rgba(22, 185, 172, 0.22);
|
||||
}
|
||||
|
||||
.mode-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user