From b6c3d8b0b3c6c23c601510163a592331a5a9f2b3 Mon Sep 17 00:00:00 2001 From: 170-carry Date: Fri, 24 Apr 2026 21:13:53 +0800 Subject: [PATCH] chore: update h5 center subpages --- h5/KYC/index.html | 24 +- h5/KYC/kyc.js | 109 +++ h5/activity/week-star/index.html | 2 +- h5/agency-center/agency-center.js | 1 + h5/agency-list/index.html | 19 +- h5/agency-list/team-list.js | 144 ++++ ...8661448.png => defaultAvatar-CdxWBK1k.png} | Bin h5/available-income/available-income.js | 381 ++++++++++ h5/available-income/index.html | 32 +- h5/bank-card/bank-card.js | 104 +++ h5/bank-card/index.html | 19 +- h5/bank-cards/bank-cards.js | 102 +++ h5/bank-cards/index.html | 29 +- h5/bd-item-distribution/index.html | 20 +- h5/bd-item-distribution/item-distribution.js | 128 ++++ h5/bd-list/bd-list.js | 129 ++++ h5/bd-list/index.html | 19 +- h5/bd-static/icons/arrow.svg | 3 + h5/bd-static/icons/back.svg | 3 + h5/bd-static/icons/search.svg | 4 + h5/bd-static/shared.css | 388 ++++++++++ h5/bd-static/shared.js | 384 ++++++++++ .../exchange-gold-coins.css | 332 +++++++++ h5/exchange-gold-coins/exchange-gold-coins.js | 442 +++++++++++ h5/exchange-gold-coins/icons/coin.svg | 5 + h5/exchange-gold-coins/index.html | 58 +- h5/host-center/host-center.js | 18 +- h5/host-setting/host-setting.css | 269 +++++++ h5/host-setting/host-setting.js | 452 ++++++++++++ h5/host-setting/icons/arrow.svg | 3 + h5/host-setting/icons/back.svg | 3 + h5/host-setting/index.html | 67 +- h5/invite-agency/index.html | 120 +-- h5/invite-agency/invite.js | 179 +++++ h5/invite-agency/yumi-invite-agency.css | 177 ----- h5/invite-agency/yumi-invite-agency.js | 694 ------------------ h5/invite-bd-leader/yumi-invite-bd-leader.js | 2 +- h5/invite-bd/index.html | 117 +-- h5/invite-bd/invite.js | 118 +++ h5/invite-bd/yumi-invite-bd.js | 2 +- .../yumi-invite-recharge-agency.js | 2 +- h5/js/imageHandler-DstQGL3Z-1776148658686.js | 2 +- h5/logic-pages/app.js | 2 +- h5/platform-policy/index.html | 22 +- h5/platform-policy/platform-policy.css | 229 ++++++ h5/platform-policy/platform-policy.js | 285 +++++++ h5/policy/index.html | 14 +- h5/policy/policy.js | 7 + h5/search-payee/icons/search.svg | 4 + h5/search-payee/index.html | 44 +- h5/search-payee/search-payee.css | 315 ++++++++ h5/search-payee/search-payee.js | 387 ++++++++++ h5/transfer/icons/dollar.svg | 5 + h5/transfer/index.html | 64 +- h5/transfer/transfer.css | 332 +++++++++ h5/transfer/transfer.js | 438 +++++++++++ h5/yumi-invite-agency/yumi-invite-agency.js | 2 +- .../yumi-invite-bd-leader.js | 2 +- h5/yumi-invite-bd/yumi-invite-bd.js | 2 +- .../yumi-invite-recharge-agency.js | 2 +- 60 files changed, 6107 insertions(+), 1155 deletions(-) create mode 100644 h5/KYC/kyc.js create mode 100644 h5/agency-list/team-list.js rename h5/assets/{defaultAvatar-CdxWBK1k-1776148661448.png => defaultAvatar-CdxWBK1k.png} (100%) create mode 100644 h5/available-income/available-income.js create mode 100644 h5/bank-card/bank-card.js create mode 100644 h5/bank-cards/bank-cards.js create mode 100644 h5/bd-item-distribution/item-distribution.js create mode 100644 h5/bd-list/bd-list.js create mode 100644 h5/bd-static/icons/arrow.svg create mode 100644 h5/bd-static/icons/back.svg create mode 100644 h5/bd-static/icons/search.svg create mode 100644 h5/bd-static/shared.css create mode 100644 h5/bd-static/shared.js create mode 100644 h5/exchange-gold-coins/exchange-gold-coins.css create mode 100644 h5/exchange-gold-coins/exchange-gold-coins.js create mode 100644 h5/exchange-gold-coins/icons/coin.svg create mode 100644 h5/host-setting/host-setting.css create mode 100644 h5/host-setting/host-setting.js create mode 100644 h5/host-setting/icons/arrow.svg create mode 100644 h5/host-setting/icons/back.svg create mode 100644 h5/invite-agency/invite.js delete mode 100644 h5/invite-agency/yumi-invite-agency.css delete mode 100644 h5/invite-agency/yumi-invite-agency.js create mode 100644 h5/invite-bd/invite.js create mode 100644 h5/platform-policy/platform-policy.css create mode 100644 h5/platform-policy/platform-policy.js create mode 100644 h5/policy/policy.js create mode 100644 h5/search-payee/icons/search.svg create mode 100644 h5/search-payee/search-payee.css create mode 100644 h5/search-payee/search-payee.js create mode 100644 h5/transfer/icons/dollar.svg create mode 100644 h5/transfer/transfer.css create mode 100644 h5/transfer/transfer.js diff --git a/h5/KYC/index.html b/h5/KYC/index.html index 3ab7dad..c28bc75 100644 --- a/h5/KYC/index.html +++ b/h5/KYC/index.html @@ -1,13 +1,29 @@ - + KYC - + - - +
+
+
Contact number
+
+
Other description
+
+ + 0/100 +
+
Passport/ID card
+
+
+
+ +
+
+
+ diff --git a/h5/KYC/kyc.js b/h5/KYC/kyc.js new file mode 100644 index 0000000..bbbb3ae --- /dev/null +++ b/h5/KYC/kyc.js @@ -0,0 +1,109 @@ +import { applyLang, bindHeader, connectToApp, get, post, safeJsonArray, t, toast } from "../bd-static/shared.js"; + +let existing = false; +let data = { id: "", contactNumber: "", category: "CARD", otherDescription: "", passportFrontUrl: "", passportBackUrl: "meaningless" }; +let images = []; +let lastImg = localStorage.getItem("lastImgUrl") || ""; + +bindHeader("kyc"); +applyLang(); +document.getElementById("contactNumber").placeholder = t("enter_contact_number"); +document.getElementById("otherDescription").placeholder = t("enter_other_description"); +document.getElementById("contactNumber").addEventListener("input", sync); +document.getElementById("otherDescription").addEventListener("input", sync); +document.getElementById("submitButton").addEventListener("click", submit); +connectToApp().then(load); + +async function load() { + try { + const response = await get("/wallet/withdraw-info/list"); + if ((response.body || []).length > 0) { + existing = true; + data = { ...response.body[0] }; + images = safeJsonArray(data.passportFrontUrl); + } + render(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function sync() { + data.contactNumber = document.getElementById("contactNumber").value.trim(); + data.otherDescription = document.getElementById("otherDescription").value; + document.getElementById("wordCount").textContent = `${data.otherDescription.length}/100`; + document.getElementById("submitButton").disabled = !(data.contactNumber && images.length > 0 && data.contactNumber.length <= 50); +} + +function render() { + document.getElementById("contactNumber").value = data.contactNumber || ""; + document.getElementById("otherDescription").value = data.otherDescription || ""; + const status = document.getElementById("statusLine"); + status.innerHTML = existing ? `${t("current_status")} ${statusText(data.status)}` : ""; + const list = document.getElementById("imageList"); + list.innerHTML = ""; + images.forEach((url, index) => { + const wrap = document.createElement("div"); + wrap.style.position = "relative"; + wrap.innerHTML = ``; + wrap.querySelector("button").addEventListener("click", () => { + images.splice(index, 1); + render(); + }); + list.appendChild(wrap); + }); + if (images.length < 3) { + const add = document.createElement("button"); + add.type = "button"; + add.className = "card"; + add.style.cssText = "width:15vw;aspect-ratio:1/1;border-radius:5vw;font-size:2em;color:#32a96b"; + add.textContent = "+"; + add.addEventListener("click", pickImage); + list.appendChild(add); + } + sync(); +} + +function statusText(status) { + return { PASS: t("review_passed"), NOT_PASS: t("review_failed"), PENDING: t("awaiting_review") }[status] || status || ""; +} + +function pickImage() { + window.renderData = receiveImage; + window.getIosAccessOriginParam = receiveImage; + if (window.FlutterPageControl) { + window.FlutterPageControl.postMessage("uploadImgFile"); + } else if (window.app && typeof window.app.getImagePath === "function") { + receiveImage(window.app.getImagePath()); + } else { + toast("uploadImgFile"); + } +} + +function receiveImage(raw) { + try { + if (!raw || raw === "\"\"") return; + const parsed = JSON.parse(raw); + const path = parsed.path || parsed.url || parsed; + if (!path || path === lastImg) return; + lastImg = path; + localStorage.setItem("lastImgUrl", path); + images.push(path); + render(); + } catch (error) { + toast(error.message); + } +} + +async function submit() { + sync(); + if (document.getElementById("submitButton").disabled) return; + data.passportFrontUrl = JSON.stringify(images); + try { + await post(existing ? "/wallet/withdraw-info/update" : "/wallet/withdraw-info/add", data); + toast(t("application_submitted")); + window.setTimeout(() => window.history.back(), 300); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} diff --git a/h5/activity/week-star/index.html b/h5/activity/week-star/index.html index 370aadf..29dc5f9 100644 --- a/h5/activity/week-star/index.html +++ b/h5/activity/week-star/index.html @@ -571,7 +571,7 @@ - +
+
+
+
+
-
+ +
+
+
+
+
+
+ diff --git a/h5/agency-list/team-list.js b/h5/agency-list/team-list.js new file mode 100644 index 0000000..e252f4f --- /dev/null +++ b/h5/agency-list/team-list.js @@ -0,0 +1,144 @@ +import { applyLang, avatarError, bindHeader, connectToApp, createMask, get, money, statusClass, statusText, t, toast } from "../bd-static/shared.js"; + +const config = { + title: "agency_list_link", + billType: "BD", + historyTitle: "revenue_details", + listSummary: (data) => `${t("team_recharge")}: ${data.totalRecharge || 0} ${t("team_salary")}: $${data.totalSalary || 0}`, + itemSide: (item) => [`${t("salary")}: $${item.teamSalaryAmount || 0}`, `${t("recharge")}: $${item.teamRechargeAmount || 0}`, `${t("host")}: ${item.teamMemberCount || 0}`], + historyEndpoint: "/team/bd/history", + moreEndpoint: (billBelong) => `/team/bd/history/more?billBelong=${encodeURIComponent(billBelong)}`, + countLabel: "agency_number" +}; + +init(config); + +async function init(settings) { + bindHeader(settings.title); + applyLang(); + document.getElementById("historyButton").addEventListener("click", () => showHistory(settings)); + try { + await connectToApp(); + const response = await get(`/team/bd/member-bill/list?type=${settings.billType}`); + renderList(response.body || {}, settings); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function renderList(data, settings) { + document.getElementById("billTitle").textContent = data.billTitle || ""; + document.getElementById("summaryLine").textContent = settings.listSummary(data); + const list = document.getElementById("memberList"); + list.innerHTML = ""; + (data.memberBillList || []).forEach((item) => { + const card = document.createElement("article"); + card.className = "member-card"; + card.innerHTML = ` + +
+
${item.agentName || "-"}
+
${t("user_id_prefix")}${item.agentAccount || "-"}
+
+
${settings.itemSide(item).map((line) => `
${line}
`).join("")}
+ `; + avatarError(card.querySelector("img")); + list.appendChild(card); + }); +} + +async function showHistory(settings) { + try { + const historyResponse = await get(settings.historyEndpoint); + const listResponse = await get(`/team/bd/member-bill/list?type=${settings.billType}`); + const history = historyResponse.body || {}; + const bill = listResponse.body || {}; + if (bill.bdHistoryCO) { + history.incomeDetails = history.incomeDetails || []; + history.incomeDetails.unshift(bill.bdHistoryCO); + } + const sheet = document.createElement("section"); + sheet.className = "sheet"; + sheet.innerHTML = ` +
${t(settings.historyTitle)}
+
+
${t("total_income")} $${money(history.totalIncome || 0)}
+
${t("previous_period_income")}: $${money(history.previousPeriodIncome || 0)}
+
+
${t("income_details_label")}
+
+ `; + createMask(sheet); + const holder = sheet.querySelector("#historyItems"); + (history.incomeDetails || []).forEach((item) => holder.appendChild(historyItem(item, settings))); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function historyItem(item, settings) { + const card = document.createElement("article"); + card.className = `salary-item ${statusClass(item.statusText)}`; + const details = item.statusText === "In Progress" + ? [ + `
${item.billTitle || ""}
`, + `
${t("team_salary")}: $${item.teamSalaryAmount || 0}${t("bd_income_salary")}: $${item.bdIncomeSalary || 0} (${item.bdRatioSalary || 0}%)
`, + `
${t("team_top_up")}: $${item.teamRechargeAmount || 0}${t("bd_income_top_up")}: $${item.bdIncomeRecharge || 0} (${item.bdRatioRecharge || 0}%)
`, + `
${t(settings.countLabel)}: ${item.agencyNumber || item.bdNumber || 0}
` + ] + : [ + `
${item.billTitle || ""}
`, + item.hitPolicyType === "TEAM_SALARY" ? `
${t("team_salary")}: $${item.teamSalaryAmount || 0}
${t("bd_income_salary")}: $${item.settlementSalary || 0} (${item.commissionRate || 0}%)
` : "", + item.hitPolicyType === "TEAM_RECHARGE" ? `
${t("team_top_up")}: $${item.teamRechargeAmount || 0}
${t("bd_income_top_up")}: $${item.settlementSalary || 0} (${item.commissionRate || 0}%)
` : "", + `
${t("num_teams") || t(settings.countLabel)} ${item.agencyNumber || item.bdNumber || 0}
` + ]; + card.innerHTML = `
${statusText(item.statusText)}
${details.join("")}`; + if (item.billBelong) { + const more = document.createElement("button"); + more.className = "more-btn"; + more.type = "button"; + more.textContent = t("more"); + more.addEventListener("click", () => showMore(item.billBelong, settings)); + card.appendChild(more); + } + return card; +} + +async function showMore(billBelong, settings) { + try { + const response = await get(settings.moreEndpoint(billBelong)); + const data = response.body || {}; + const sheet = document.createElement("section"); + sheet.className = "sheet"; + sheet.innerHTML = ` +
${data.period || ""}
+
+ ${t("team_top_up")}: $${data.teamTotalRecharge || data.teamRecharge || 0} + ${t("total_team_salary")}: $${data.teamTotalSalary || data.teamSalary || 0} +
+
+ `; + createMask(sheet); + const list = sheet.querySelector("#moreMembers"); + (data.memberDetails || []).forEach((item) => { + const card = document.createElement("article"); + card.className = "member-card"; + card.innerHTML = ` + +
+
${item.userName || item.bdUserName || "-"}
+
${t("user_id_prefix")}${item.account || "-"}
+
+
+
${t("salary")}: $${item.salary || 0}
+
${t("recharge")}: $${item.recharge || 0}
+
${t("host")}: ${item.hostCount || item.agencyCount || 0}
+
+ `; + avatarError(card.querySelector("img")); + list.appendChild(card); + }); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} diff --git a/h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png b/h5/assets/defaultAvatar-CdxWBK1k.png similarity index 100% rename from h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png rename to h5/assets/defaultAvatar-CdxWBK1k.png diff --git a/h5/available-income/available-income.js b/h5/available-income/available-income.js new file mode 100644 index 0000000..a0aaab5 --- /dev/null +++ b/h5/available-income/available-income.js @@ -0,0 +1,381 @@ +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("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 = ` +
+ ${rows.map((row, index) => ``).join("")} +
+
${rows[state.salaryTab].detail}
+
$${money(rows[state.salaryTab].salary)}
+ `; + 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) => ``).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 = ` +
+ ${transfers.map((item) => ``).join("")} +
+ +
+ `; + 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")); + document.getElementById("transferButton").addEventListener("click", submitTransfer); +} + +function payeeHtml(payee) { + return ` + + + + ${payee.name || "-"} + ${t("user_id_prefix")}${payee.account || "-"} + + + `; +} + +function renderExchange() { + const panel = document.getElementById("actionPanel"); + panel.innerHTML = ` +
+ ${exchanges.map((item) => ``).join("")} +
+
+ `; + 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 = ` +
+ ${t("kyc")}${state.kyc ? `: ${statusLabel(status)}` : ""} +
+
+ ${state.kyc ? kycHtml(state.kyc) : ``} +
+
+ +
+ `; + const completeKyc = document.getElementById("completeKyc"); + if (completeKyc) completeKyc.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 `${t("review_passed")}`; + if (status === "NOT_PASS") return `${t("review_failed")}`; + if (status === "PENDING") return `${t("awaiting_review")}`; + return ""; +} + +function kycHtml(item) { + return ` +
+
${t("passport_id_card_title")}:
+
${(item.previewUrls || []).map((src) => ``).join("")}
+
${t("contact_number")}:
${item.contactNumber || ""}
+
${t("other_description")}:
${item.otherDescription || ""}
+
+ `; +} + +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 = ` +
${t("tips")}
+
${t("confirm_exchange")}
+
+ + +
+ `; + 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 = ` +
${t("prompt")}
+
${t("fill_info_tips")}
+
${t("card_issue_tips")}
+ + `; + 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 = ` +
${t("cash_out")}
+
+
${t("withdrawal_method")}: ${state.bankCard?.cardType || ""}
+ ${state.bankCard ? `
${t("card_number")}: ${state.bankCard.cardNo || ""}
` : ""} + +
+
+
${t("cash_out_minimum")}
+
+ `; + 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 = `
${t("tips")}
${t("cash_withdrawal_tips")}
`; + createMask(panel); +} + +init(); diff --git a/h5/available-income/index.html b/h5/available-income/index.html index b07f2ec..89e9561 100644 --- a/h5/available-income/index.html +++ b/h5/available-income/index.html @@ -1,13 +1,35 @@ - + - Available Income - + Available Salaries + + - - +
+
+
+

Available salaries

+ +
+
+ $0 +
+ + +
+
+
+ diff --git a/h5/bank-card/bank-card.js b/h5/bank-card/bank-card.js new file mode 100644 index 0000000..b332a09 --- /dev/null +++ b/h5/bank-card/bank-card.js @@ -0,0 +1,104 @@ +import { applyLang, bindHeader, connectToApp, createMask, get, getUserId, post, t, toast } from "../bd-static/shared.js"; + +let ways = []; +let cards = []; +let form = { userId: getUserId(), cardType: "", cardName: "", cardNo: "", payee: "" }; + +bindHeader("bank_card"); +applyLang(); +document.getElementById("addButton").addEventListener("click", openAddSheet); +connectToApp().then(loadData); + +async function loadData() { + try { + const [wayResponse, cardResponse] = await Promise.all([get("/wallet/bank/withdrawal_ways"), get("/wallet/bank-card/list")]); + ways = wayResponse.body || []; + cards = cardResponse.body || []; + render(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function render() { + const current = cards.find((item) => item.use); + document.getElementById("currentCard").innerHTML = current + ? `
${t("payee")}: ${current.payee || "-"}
${t("card_number")}: ${current.cardNo || "-"}
` + : `
${t("no_bank_cards_in_use")}
`; + const list = document.getElementById("cardList"); + list.innerHTML = ""; + if (!cards.length) { + list.innerHTML = `
${t("no_bank_cards_added")}
`; + return; + } + cards.forEach((item) => { + const card = document.createElement("article"); + card.className = "card card-pad"; + card.style.marginBottom = "10px"; + card.innerHTML = ` +
${t("payee")}: ${item.payee || "-"}
+
${t("card_number")}: ${item.cardNo || "-"}
+
+ + ${item.use ? `` : ``} +
+ `; + card.querySelector("[data-delete]").addEventListener("click", () => deleteCard(item.id)); + const use = card.querySelector("[data-use]"); + if (use) use.addEventListener("click", () => useCard(item.id)); + list.appendChild(card); + }); +} + +function openAddSheet() { + form = { userId: getUserId(), cardType: "", cardName: "", cardNo: "", payee: "" }; + const sheet = document.createElement("section"); + sheet.className = "sheet"; + sheet.innerHTML = ` +
${t("add_new_payment_method")}
+
${t("withdrawal_method")}:
+
+ +
${t("card_number")}:
+
${t("payee")}:
+
+ `; + createMask(sheet); + const type = sheet.querySelector("#cardType"); + type.addEventListener("change", () => { + form.cardType = type.value; + form.cardName = type.value === "USDT" ? "USDT" : ""; + sheet.querySelector("#cardNameWrap").hidden = type.value !== "BANK"; + }); + sheet.querySelector("#confirmAdd").addEventListener("click", async () => { + form.cardName = type.value === "BANK" ? sheet.querySelector("#cardName").value.trim() : form.cardName; + form.cardNo = sheet.querySelector("#cardNo").value.trim(); + form.payee = sheet.querySelector("#payee").value.trim(); + if (!form.cardType || !form.cardName || !form.cardNo || !form.payee) return; + try { + await post("/wallet/bank-card/add", form); + document.getElementById("modalMask").remove(); + await loadData(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } + }); +} + +async function deleteCard(id) { + try { + await post("/wallet/bank-card/delete", { id }); + await loadData(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +async function useCard(id) { + try { + await post("/wallet/bank-card/use", { id }); + await loadData(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} diff --git a/h5/bank-card/index.html b/h5/bank-card/index.html index bd1f6a8..0eebe59 100644 --- a/h5/bank-card/index.html +++ b/h5/bank-card/index.html @@ -1,13 +1,24 @@ - + Bank Card - + - - +
+
+
Currently in use
+
+
All bank cards
+
+
+
+
+ +
+
+ diff --git a/h5/bank-cards/bank-cards.js b/h5/bank-cards/bank-cards.js new file mode 100644 index 0000000..404fd12 --- /dev/null +++ b/h5/bank-cards/bank-cards.js @@ -0,0 +1,102 @@ +import { applyLang, bindHeader, connectToApp, get, navigateTo, safeJsonArray, t, toast } from "../bd-static/shared.js"; + +const state = { card: null, kyc: null }; +const bankCard = document.getElementById("bankCard"); +const moreCards = document.getElementById("moreCards"); +const kycCard = document.getElementById("kycCard"); +const kycStatus = document.getElementById("kycStatus"); +const kycMore = document.getElementById("kycMore"); +const kycColon = document.getElementById("kycColon"); +const query = new URLSearchParams(window.location.search); + +bindHeader("bank_cards_link"); +applyLang(); +moreCards.addEventListener("click", () => navigateTo("/bank-card")); +kycMore.addEventListener("click", () => navigateTo("/KYC")); + +async function init() { + try { + await connectToApp(); + await Promise.all([loadCards(), loadKyc()]); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } finally { + render(); + } +} + +async function loadCards() { + const response = await get("/wallet/bank-card/list"); + const body = response.status && Array.isArray(response.body) ? response.body : []; + state.card = body.find((item) => item.use) || null; +} + +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; +} + +function render() { + moreCards.hidden = !state.card; + bankCard.innerHTML = state.card + ? ` +
+
${t("category")}
+
${state.card.cardType || ""}
+
${t("card_number_label")}
+
${state.card.cardNo || ""}
+
${t("payee")}:
+
${state.card.payee || ""}
+
+ ` + : ` + + `; + const addCard = document.getElementById("addCard"); + if (addCard) addCard.addEventListener("click", () => navigateTo("/bank-card")); + + kycColon.hidden = !state.kyc; + kycMore.hidden = !state.kyc; + kycStatus.innerHTML = statusHtml(state.kyc?.status); + kycCard.innerHTML = state.kyc ? kycDetails(state.kyc) : ` + + `; + const completeKyc = document.getElementById("completeKyc"); + if (completeKyc) completeKyc.addEventListener("click", () => navigateTo("/KYC")); + + const from = query.get("from"); + const activeAction = query.get("activeAction"); + if (from || activeAction) { + sessionStorage.setItem("bankCardsBackQuery", JSON.stringify({ from, activeAction })); + } +} + +function statusHtml(status) { + if (status === "PASS") return `${t("review_passed")}`; + if (status === "NOT_PASS") return `${t("review_failed")}`; + if (status === "PENDING") return `${t("awaiting_review")}`; + return ""; +} + +function kycDetails(item) { + const images = (item.previewUrls || []).map((src) => ``).join(""); + return ` +
+
${t("passport_id_card_title")}:
+
${images}
+
${t("contact_number")}:
+
${item.contactNumber || ""}
+
${t("other_description")}:
+
${item.otherDescription || ""}
+
+ `; +} + +init(); diff --git a/h5/bank-cards/index.html b/h5/bank-cards/index.html index f854e32..280abef 100644 --- a/h5/bank-cards/index.html +++ b/h5/bank-cards/index.html @@ -1,13 +1,34 @@ - + Bank Cards - + - - +
+
+
+
+

Bank Card

+ +
+
+
+
+
+
+

KYC

+ + +
+ +
+
+
+
+
+ diff --git a/h5/bd-item-distribution/index.html b/h5/bd-item-distribution/index.html index 8bb6e2d..f68d4e3 100644 --- a/h5/bd-item-distribution/index.html +++ b/h5/bd-item-distribution/index.html @@ -1,13 +1,25 @@ - + BD Item Distribution - + + - - +
+
+
+
+ diff --git a/h5/bd-item-distribution/item-distribution.js b/h5/bd-item-distribution/item-distribution.js new file mode 100644 index 0000000..aac6cfc --- /dev/null +++ b/h5/bd-item-distribution/item-distribution.js @@ -0,0 +1,128 @@ +import { applyLang, bindHeader, connectToApp, createMask, get, post, t, toast } from "../bd-static/shared.js"; + +const state = { + tabs: [], + active: 0, + goods: [], + selected: null, + locked: true +}; + +const tabs = document.getElementById("tabs"); +const goods = document.getElementById("goods"); +const typeMap = { frames: "AVATAR_FRAME", vehicles: "RIDE", vip: "NOBLE_VIP" }; + +bindHeader("item_distribution"); +applyLang(); + +async function init() { + try { + await connectToApp(); + await loadPermissions(); + renderTabs(); + if (state.tabs.length) await loadGoods(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +async function loadPermissions() { + const response = await get("/sys/bd/list-permission"); + const body = response.status && Array.isArray(response.body) ? response.body : []; + const next = []; + if (body.includes("AVATAR_FRAME")) next.push({ id: 1, name: "frames" }); + if (body.includes("RIDE")) next.push({ id: 2, name: "vehicles" }); + if (body.some((item) => item.startsWith("NOBLE_VIP"))) next.push({ id: 3, name: "vip" }); + state.tabs = next; + state.active = 0; +} + +async function loadGoods() { + const tab = state.tabs[state.active]; + if (!tab) { + state.goods = []; + renderGoods(); + return; + } + const response = await get(`/sys/bd/props?propsType=${typeMap[tab.name]}¤cyType=GOLD`); + state.goods = response.status && Array.isArray(response.body) ? response.body : []; + renderGoods(); +} + +function renderTabs() { + tabs.innerHTML = ""; + state.tabs.forEach((tab, index) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = `tab${state.active === index ? " active" : ""}`; + button.textContent = t(tab.name); + button.addEventListener("click", async () => { + if (state.active === index) return; + state.active = index; + renderTabs(); + await loadGoods(); + }); + tabs.appendChild(button); + }); + if (state.tabs.length) { + const line = document.createElement("div"); + line.className = "tab-line"; + line.style.left = `calc(${(state.active + 0.5) * (100 / state.tabs.length)}% - 7.5px)`; + tabs.appendChild(line); + } +} + +function renderGoods() { + goods.innerHTML = ""; + state.goods.forEach((item) => { + const card = document.createElement("button"); + card.type = "button"; + card.className = "goods-card"; + card.innerHTML = ` + + ${t("send")} + `; + card.addEventListener("click", () => openSend(item)); + goods.appendChild(card); + }); +} + +function openSend(item) { + state.selected = item; + state.locked = true; + const panel = document.createElement("section"); + panel.className = "card card-pad"; + panel.style.cssText = "width:90%;margin:20vh auto 0;display:flex;flex-direction:column;align-items:center;gap:12px"; + panel.innerHTML = ` + +
${t("validity")}: 3${t("day")}
+
+ + +
+ `; + const mask = createMask(panel); + const input = panel.querySelector("#giftUserId"); + panel.querySelector("#confirmGift").addEventListener("click", () => sendGift(input.value.trim(), mask)); +} + +async function sendGift(account, mask) { + if (!state.locked) return; + state.locked = false; + try { + const user = await get(`/user/user-profile/open-search?account=${encodeURIComponent(account)}`); + if (user.status && user.body) { + const response = await post("/sys/bd/send", { propsId: state.selected.id, acceptUserId: user.body.id }); + if (response.errorCode === 0 && response.status) { + mask.remove(); + toast(t("gift_delivery_successful")); + state.selected = null; + } + } + } catch (error) { + toast(error.response?.errorMsg || error.message); + state.locked = true; + } +} + +init(); diff --git a/h5/bd-list/bd-list.js b/h5/bd-list/bd-list.js new file mode 100644 index 0000000..fa846cf --- /dev/null +++ b/h5/bd-list/bd-list.js @@ -0,0 +1,129 @@ +import { applyLang, avatarError, bindHeader, connectToApp, createMask, get, money, statusClass, statusText, t, toast } from "../bd-static/shared.js"; + +bindHeader("bd_list_link"); +applyLang(); +document.getElementById("historyButton").addEventListener("click", showHistory); +init(); + +async function init() { + try { + await connectToApp(); + const response = await get("/team/bd/member-bill/list?type=BDLEADER"); + renderList(response.body || {}); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function renderList(data) { + document.getElementById("billTitle").textContent = data.billTitle || ""; + document.getElementById("summaryLine").textContent = `${t("number_of_bd")}: ${data.memberCount || 0} ${t("team_salary")}: $${data.totalSalary || 0}`; + const list = document.getElementById("memberList"); + list.innerHTML = ""; + (data.memberBillList || []).forEach((item) => { + const card = document.createElement("article"); + card.className = "member-card"; + card.innerHTML = ` + +
+
${item.agentName || "-"}
+
${t("user_id_prefix")}${item.agentAccount || "-"}
+
+
+
${t("team_salary")}: $${item.teamSalaryAmount || 0}
+
${t("agencies")}: ${item.teamMemberCount || 0}
+
+ `; + avatarError(card.querySelector("img")); + list.appendChild(card); + }); +} + +async function showHistory() { + try { + const historyResponse = await get("/team/bd/leader/history"); + const listResponse = await get("/team/bd/member-bill/list?type=BDLEADER"); + const history = historyResponse.body || {}; + const bill = listResponse.body || {}; + if (bill.bdHistoryCO) { + history.incomeDetails = history.incomeDetails || []; + history.incomeDetails.unshift(bill.bdHistoryCO); + } + const sheet = document.createElement("section"); + sheet.className = "sheet"; + sheet.innerHTML = ` +
${t("bd_leader_history")}
+
+
${t("total_income")} $${money(history.totalIncome || 0)}
+
${t("previous_period_income")}: $${money(history.previousPeriodIncome || 0)}
+
+
${t("income_details_label")}
+
+ `; + createMask(sheet); + const holder = sheet.querySelector("#historyItems"); + (history.incomeDetails || []).forEach((item) => holder.appendChild(historyItem(item))); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +function historyItem(item) { + const card = document.createElement("article"); + card.className = `salary-item ${statusClass(item.statusText)}`; + card.innerHTML = ` +
${statusText(item.statusText)}
+
${item.billTitle || ""}
+
+ ${t("bd_number")}: ${item.bdNumber || 0} + ${t("team_salary")}: $${item.teamSalaryAmount || 0} +
+ `; + if (item.statusText === "Completed") { + const more = document.createElement("button"); + more.className = "more-btn"; + more.type = "button"; + more.textContent = t("more"); + more.addEventListener("click", () => showMore(item.billBelong)); + card.appendChild(more); + } + return card; +} + +async function showMore(billBelong) { + try { + const response = await get(`/team/bd/leader/history/more?billBelong=${encodeURIComponent(billBelong)}`); + const data = response.body || {}; + const sheet = document.createElement("section"); + sheet.className = "sheet"; + sheet.innerHTML = ` +
${data.period || ""}
+
+ ${t("team_salary")}: $${data.teamSalary || 0} + ${t("bd_number")}: ${data.bdNumber || 0} +
+
+ `; + createMask(sheet); + const list = sheet.querySelector("#moreMembers"); + (data.memberDetails || []).forEach((item) => { + const card = document.createElement("article"); + card.className = "member-card"; + card.innerHTML = ` + +
+
${item.bdUserName || "-"}
+
${t("user_id_prefix")}${item.account || "-"}
+
+
+
${t("salary")}: $${item.salary || 0}
+
${t("agencies")}: ${item.agencyCount || 0}
+
+ `; + avatarError(card.querySelector("img")); + list.appendChild(card); + }); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} diff --git a/h5/bd-list/index.html b/h5/bd-list/index.html index ce5d8d0..7b70ce8 100644 --- a/h5/bd-list/index.html +++ b/h5/bd-list/index.html @@ -1,13 +1,24 @@ - + BD List - + - - +
+
+
+
+
-
+ +
+
+
+
+
+
+ diff --git a/h5/bd-static/icons/arrow.svg b/h5/bd-static/icons/arrow.svg new file mode 100644 index 0000000..425b0a7 --- /dev/null +++ b/h5/bd-static/icons/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/bd-static/icons/back.svg b/h5/bd-static/icons/back.svg new file mode 100644 index 0000000..80e8391 --- /dev/null +++ b/h5/bd-static/icons/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/bd-static/icons/search.svg b/h5/bd-static/icons/search.svg new file mode 100644 index 0000000..142c257 --- /dev/null +++ b/h5/bd-static/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/h5/bd-static/shared.css b/h5/bd-static/shared.css new file mode 100644 index 0000000..f229b5e --- /dev/null +++ b/h5/bd-static/shared.css @@ -0,0 +1,388 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.4; +} + +html, +body { + width: 100%; + min-height: 100%; + background: #f4fbf6; + color: #1f2937; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + +button, +input, +textarea { + font: inherit; +} + +button { + border: 0; + background: transparent; + color: inherit; +} + +.page { + min-height: 100vh; + background: #f4fbf6; +} + +.header { + position: sticky; + top: 0; + z-index: 20; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.header h1 { + min-width: 0; + text-align: center; + font-size: 1.35em; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.icon-btn, +.lang-btn { + min-width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + font-weight: 700; +} + +.icon-btn { + padding: 0; +} + +.icon-btn img { + width: 24px; + height: 24px; +} + +.lang-btn { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #e9f7ee; + color: #276344; + font-size: 0.86em; +} + +.content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} + +.card { + border: 1px solid #d7eadf; + border-radius: 16px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + overflow: hidden; +} + +.card-pad { + padding: 16px; +} + +.section-title { + font-size: 1.35em; + font-weight: 700; +} + +.subtle { + color: rgba(0, 0, 0, 0.45); + font-weight: 600; +} + +.row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.divider { + border-bottom: 1px solid #e6e6e6; +} + +.member-card { + display: flex; + align-items: center; + gap: 10px; + padding: 12px; + border-radius: 12px; + background: #fff8df; +} + +.member-card + .member-card { + margin-top: 10px; +} + +.avatar { + width: 3.5em; + height: 3.5em; + flex: 0 0 auto; + border-radius: 50%; + object-fit: cover; + background: #e7efe9; +} + +.member-main { + flex: 1; + min-width: 0; +} + +.member-name { + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-meta, +.member-side { + color: rgba(0, 0, 0, 0.48); + font-size: 0.86em; + font-weight: 600; +} + +.member-side { + flex: 0 0 auto; + text-align: right; + color: #1f2937; +} + +.menu-link { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 14px 0; + text-align: left; + font-weight: 700; +} + +.arrow { + width: 22px; + height: 22px; + flex: 0 0 auto; +} + +.field { + width: 100%; + border: 0; + outline: 0; + background: transparent; + color: #1f2937; + font-weight: 600; +} + +.input-box { + padding: 10px 12px; + border: 1px solid #d7eadf; + border-radius: 8px; + background: #fff; +} + +.primary-btn, +.danger-btn, +.soft-btn { + min-height: 40px; + border-radius: 999px; + padding: 8px 16px; + color: #fff; + font-weight: 700; + text-align: center; +} + +.primary-btn { + background: #32a96b; +} + +.danger-btn { + background: #ef4444; +} + +.soft-btn { + background: #9ca3af; +} + +.primary-btn:disabled, +.soft-btn:disabled { + background: #cfd8d2; +} + +.badge { + display: inline-flex; + padding: 3px 8px; + border-radius: 999px; + color: #fff; + background: #3d73ff; + font-size: 0.8em; + font-weight: 700; +} + +.badge.pass, +.badge.success { + background: #36b56b; +} + +.badge.fail, +.badge.pending { + background: #ef4444; +} + +.badge.wait { + background: #d59a20; +} + +.mask { + position: fixed; + inset: 0; + z-index: 50; + background: rgba(0, 0, 0, 0.35); +} + +.sheet { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 51; + max-height: 82vh; + overflow: auto; + padding: 16px; + border-radius: 16px 16px 0 0; + background: #fffbe3; +} + +.sheet-title { + margin-bottom: 10px; + color: #333; + font-size: 1.15em; + font-weight: 700; + text-align: center; +} + +.salary-item { + position: relative; + margin-top: 8px; + padding: 32px 10px 30px; + border-radius: 8px; + background: #ebf1fd; + font-size: 0.9em; +} + +.salary-item.pending { + background: #feebef; +} + +.salary-item.completed { + background: #e8faed; +} + +.salary-item.out { + background: #e9e9e9; +} + +.status-tag { + position: absolute; + top: 0; + left: 0; + min-width: 7.5em; + padding: 2px 8px; + border-radius: 8px 0 8px 0; + background: #3d73ff; + color: #fff; + font-weight: 700; + text-align: center; +} + +.salary-item.pending .status-tag { + background: #ff3d40; +} + +.salary-item.completed .status-tag { + background: #32b968; +} + +.salary-item.out .status-tag { + background: #9ca3af; +} + +.more-btn { + position: absolute; + right: 10px; + bottom: 7px; + color: #4f46e5; + font-weight: 700; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(28px + env(safe-area-inset-bottom)); + z-index: 80; + max-width: 86vw; + transform: translateX(-50%); + padding: 10px 14px; + border-radius: 8px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + font-size: 13px; + font-weight: 500; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .arrow, +[dir="rtl"] .flip-img { + transform: scaleX(-1); +} + +[dir="rtl"] .member-side { + text-align: left; +} + +@media screen and (max-width: 360px) { + .page { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + .page { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + .page { + font-size: 24px; + } +} diff --git a/h5/bd-static/shared.js b/h5/bd-static/shared.js new file mode 100644 index 0000000..fd3eadc --- /dev/null +++ b/h5/bd-static/shared.js @@ -0,0 +1,384 @@ +export const API_BASE_URL = "https://jvapi.haiyihy.com"; + +const text = { + en: { + bd_leader_center: "BD Leader Center", + available_salaries: "Available salaries", + bd: "BD", + bd_leader: "BD Leader", + my_bd_salary: "My BD salary", + my_bd_leader_salary: "My BD leader salary", + exchange: "Exchange", + transfer: "Transfer", + cash_out: "Cash out", + details: "Details", + please_select_amount: "Please select amount", + please_select_coin_amount: "Please select coin amount", + please_select_payee: "Please select payee", + transfer_dollar_success: "Transfer succeeded", + transfer_dollar_failed: "Transfer failed", + transfer_error: "Transfer error", + exchange_success: "Exchange succeeded", + exchange_error_occurred: "Exchange failed", + amount: "Amount", + coins: "Coins", + dollar: "Dollar", + minimum_cash_out: "Minimum cash out is $50", + bd_policy_link: "BD policy", + bd_leader_policy: "BD leader policy", + bd_policy: "BD policy", + agency_list_link: "Agency list", + bd_list_link: "BD List", + invite_become_agent: "Invite to become agent", + invite_become_BD: "Invite to become BD", + bank_cards_link: "Bank cards", + bank_card: "Bank Card", + kyc: "KYC", + category: "Category", + card_number_label: "Card number", + card_number: "Card number", + payee: "Payee", + more: "More", + add_payment_method: "Add payment method", + complete_information: "Complete information", + passport_id_card_title: "Passport/ID card", + contact_number: "Contact number", + other_description: "Other description", + review_passed: "Review passed", + review_failed: "Review failed", + awaiting_review: "Awaiting review", + currently_in_use: "Currently in use", + all_bank_cards: "All bank cards", + no_bank_cards_in_use: "No bank cards in use", + no_bank_cards_added: "No bank cards added", + add_new_payment_method: "Add new payment method", + withdrawal_method: "Withdrawal method", + choose_withdrawal_method: "Choose withdrawal method", + card_name: "Card name", + enter_card_name: "Enter card name", + enter_card_number: "Enter card number", + enter_payee: "Enter payee", + delete: "Delete", + use: "Use", + using: "Using", + confirm: "Confirm", + submit: "Submit", + change_kyc: "Change KYC", + enter_contact_number: "Enter contact number", + enter_other_description: "Enter other description", + current_status: "Current status", + tips: "Tips", + confirm_modify_kyc: "Confirm modify KYC?", + cancel: "Cancel", + application_submitted: "Application submitted", + agency_number: "Agency number", + number_of_bd: "BD number", + bd_number: "BD number", + team_salary: "Team salary", + team_recharge: "Team recharge", + salary: "Salary", + recharge: "Recharge", + host: "Host", + agencies: "Agencies", + user_id_prefix: "ID: ", + revenue_details: "Revenue details", + bd_leader_history: "BD Leader history", + total_income: "Total income", + previous_period_income: "Previous period income", + income_details_label: "Income details", + team_top_up: "Team top up", + bd_income_salary: "BD income salary", + bd_income_top_up: "BD income top up", + total_team_salary: "Total team salary", + num_teams: "Num teams", + in_progress: "In Progress", + pending: "Pending", + completed: "Completed", + out_of_account: "Out of account", + information: "Information", + enter_user_id: "Enter User ID", + invite: "Invite", + no_users_found: "No users found", + success: "Success", + invitation_submitted: "Invitation submitted", + application_cancelled: "Application cancelled", + something_went_wrong: "Something went wrong", + cancellation_failed_try_again: "Cancellation failed, try again" + , + item_distribution: "Item distribution", + frames: "Frames", + vehicles: "Vehicles", + vip: "VIP", + send: "Send", + validity: "Validity", + day: " day", + enter_user_id_placeholder: "Enter user ID", + gift_delivery_successful: "Gift delivery successful", + available_salaries: "Available salaries", + details: "Details", + bd: "BD", + bd_leader: "BD Leader", + my_bd_salary: "My BD salary", + my_bd_leader_salary: "My BD leader salary", + exchange: "Exchange", + transfer: "Transfer", + cash_out: "Cash out", + please_select_amount: "Please select amount", + please_select_payee: "Please select payee", + transfer_dollar_success: "Transfer successful", + transfer_dollar_failed: "Transfer failed", + transfer_error: "Transfer error", + please_select_coin_amount: "Please select coin amount", + exchange_success: "Exchange successful", + exchange_error_occurred: "Exchange failed", + confirm_exchange: "Confirm exchange?", + fill_info_tips: "Please complete bank card and KYC information", + card_issue_tips: "Bank card information is required before cash out", + to_improve: "To improve", + cash_withdrawal_tips: "Cash withdrawal submitted", + enter_cash_out_amount: "Enter cash out amount", + cash_out_minimum: "Cash out minimum: $50", + change_withdrawal_method: "Change withdrawal method", + please_select_bank_card: "Please select bank card", + prompt: "Prompt", + failed_to_load_balance: "Failed to load balance" + }, + ar: {}, + tr: {}, + bn: {} +}; + +["ar", "tr", "bn"].forEach((lang) => { + text[lang] = { ...text.en }; +}); + +let headers = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai", + "content-type": "application/json" +}; + +export const lang = resolveLanguage(); + +export function t(key) { + return text[lang]?.[key] || text.en[key] || key; +} + +export function applyLang() { + document.documentElement.lang = lang; + document.documentElement.dir = lang === "ar" ? "rtl" : "ltr"; + document.querySelectorAll("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); + const language = document.querySelector("[data-action='language']"); + if (language) language.textContent = lang.toUpperCase(); +} + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const hash = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : ""; + const hashParams = new URLSearchParams(hash); + const value = params.get("lang") || hashParams.get("lang") || navigator.language || "en"; + if (value.startsWith("ar")) return "ar"; + if (value.startsWith("tr")) return "tr"; + if (value.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAppHeaders(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + authorization: data.Authorization, + reqLang: data["Req-Lang"], + appVersion: appIntel.version, + buildVersion: appIntel.build, + channel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + origin: sysOrigin.origin, + child: sysOrigin.child + }; +} + +export function setHeadersFromApp(rawOrObject) { + const data = typeof rawOrObject === "string" ? parseAppHeaders(rawOrObject) : rawOrObject || {}; + const next = {}; + if (data.authorization) next.authorization = data.authorization; + if (data.reqLang) next["req-lang"] = data.reqLang; + if (data.appVersion || data.buildVersion || data.channel) { + next["req-app-intel"] = [ + data.appVersion ? `version=${data.appVersion}` : "", + data.buildVersion ? `build=${data.buildVersion}` : "", + data.channel ? `channel=${data.channel}` : "" + ].filter(Boolean).join(";"); + } + if (data.reqImei) next["req-imei"] = data.reqImei; + if (data.origin) next["req-sys-origin"] = [`origin=${data.origin}`, data.child ? `child=${data.child}` : ""].filter(Boolean).join(";"); + headers = { ...headers, ...next }; +} + +export async function connectToApp() { + window.renderData = (raw) => raw && setHeadersFromApp(raw); + window.getIosAccessOriginParam = window.renderData; + if (window.app && typeof window.app.getAccessOrigin === "function") { + const raw = window.app.getAccessOrigin(); + if (raw) setHeadersFromApp(raw); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + setHeadersFromApp({ reqLang: lang, authorization: "" }); +} + +export async function request(method, path, body) { + const options = { method, headers }; + if (body !== undefined) options.body = JSON.stringify(body); + const response = await fetch(`${API_BASE_URL}${path}`, options); + 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; +} + +export const get = (path) => request("GET", path); +export const post = (path, body) => request("POST", path, body); + +export function navigateTo(target) { + const url = new URL(target, window.location.origin); + const currentLang = new URLSearchParams(window.location.search).get("lang"); + if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang); + window.location.href = `${url.pathname}${url.search}${url.hash}`; +} + +export function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +export function bindHeader(titleKey) { + document.body.insertAdjacentHTML("afterbegin", ` +
+ +

${t(titleKey)}

+ +
+ `); + document.querySelector("[data-action='back']").addEventListener("click", closePage); + document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language")); + applyLang(); +} + +export function money(value) { + if (value === null || value === undefined || value === "") return "0"; + const number = Number(value); + if (Number.isNaN(number)) return String(value); + return Number.isInteger(number) ? String(number) : number.toFixed(2); +} + +export function safeJsonArray(value) { + try { + const result = JSON.parse(value || "[]"); + return Array.isArray(result) ? result : []; + } catch { + return []; + } +} + +export function getUserId() { + const keys = ["userInfo", "user", "USER_INFO"]; + for (const key of keys) { + try { + const raw = localStorage.getItem(key) || sessionStorage.getItem(key); + if (!raw) continue; + const data = JSON.parse(raw); + if (data.id || data.userId) return data.id || data.userId; + } catch {} + } + return ""; +} + +export function toast(message) { + let node = document.getElementById("toast"); + if (!node) { + node = document.createElement("div"); + node.id = "toast"; + node.className = "toast"; + document.body.appendChild(node); + } + node.textContent = message; + node.hidden = false; + window.clearTimeout(toast.timer); + toast.timer = window.setTimeout(() => { + node.hidden = true; + }, 2500); +} + +export function createMask(content) { + const old = document.getElementById("modalMask"); + if (old) old.remove(); + const mask = document.createElement("div"); + mask.id = "modalMask"; + mask.className = "mask"; + mask.addEventListener("click", () => mask.remove()); + content.addEventListener("click", (event) => event.stopPropagation()); + mask.appendChild(content); + document.body.appendChild(mask); + return mask; +} + +export function statusClass(status) { + if (status === "Completed") return "completed"; + if (status === "Pending") return "pending"; + if (status === "Out of account" || status === "HANG_UP") return "out"; + return ""; +} + +export function statusText(status) { + const map = { + SETTLED: t("completed"), + PAY_OUT: t("completed"), + UNPAID: t("in_progress"), + HANG_UP: t("out_of_account"), + "In Progress": t("in_progress"), + Pending: t("pending"), + Completed: t("completed"), + "Out of account": t("out_of_account") + }; + return map[status] || status || ""; +} + +export function avatarError(img) { + img.addEventListener("error", () => { + img.removeAttribute("src"); + }); +} diff --git a/h5/exchange-gold-coins/exchange-gold-coins.css b/h5/exchange-gold-coins/exchange-gold-coins.css new file mode 100644 index 0000000..292ccf4 --- /dev/null +++ b/h5/exchange-gold-coins/exchange-gold-coins.css @@ -0,0 +1,332 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.35; +} + +html, +body { + width: 100%; + min-height: 100%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; +} + +body { + color: rgba(0, 0, 0, 0.8); + background: #fff; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.exchange-gold-coins { + width: 100vw; + min-height: 100vh; + background-color: #fff; + background-image: url("/assets/secondBg-CSqvFWr0-1776148661447.png"); + background-size: cover; + background-position: top center; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: rgba(255, 255, 255, 0.96); +} + +.page-header h1 { + min-width: 0; + color: #111; + font-size: 1.25em; + font-weight: 700; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.back-button, +.lang-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; +} + +.back-button { + padding: 6px; +} + +.back-button img { + width: 100%; + height: 100%; +} + +.lang-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + color: #1f6f46; + font-size: 0.86em; + font-weight: 700; +} + +.content { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.section-title { + color: rgba(0, 0, 0, 0.8); + font-weight: 700; +} + +.details-button { + display: flex; + align-items: center; + color: rgba(0, 0, 0, 0.42); + font-weight: 600; +} + +.details-button img { + width: 16px; + margin-left: 4px; +} + +.surface-card { + margin-bottom: 12px; + border: 1px solid rgba(194, 217, 240, 0.82); + border-radius: 20px; + background: #fff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.salary-card { + padding: 16px; +} + +.salary-label { + font-size: 1em; + font-weight: 600; +} + +.salary-value { + margin-left: 6px; + color: #333; + font-size: 1.1em; + font-weight: 800; +} + +.loading-text { + margin-left: 6px; + color: #1f6f46; + font-weight: 700; +} + +.coins-title { + margin-bottom: 10px; +} + +.coins-card { + padding: 16px; +} + +.coin-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.coin-item { + aspect-ratio: 1 / 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 6px; + border: 2px solid #ffdb81; + border-radius: 12px; + background: #ffe5a1; + cursor: pointer; + transition: transform 0.2s, border-color 0.2s, background-color 0.2s; +} + +.coin-item.selected { + border-color: #ffb700; + background: #fdca49; +} + +.coin-item:active { + transform: scale(0.98); +} + +.coin-item img { + width: 40%; +} + +.coin-price { + color: #131111; + font-weight: 600; +} + +.action-row { + display: flex; + justify-content: center; + margin-top: 10px; +} + +.exchange-button { + width: 70%; + min-height: 44px; + border-radius: 32px; + background: #ff9500; + color: #fff; + font-weight: 700; + cursor: pointer; +} + +.exchange-button:disabled { + background: #ccc; + cursor: not-allowed; +} + +.modal-layer { + position: fixed; + inset: 0; + z-index: 20; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); +} + +.modal-card { + width: 80%; + max-width: 420px; + display: flex; + flex-direction: column; + align-items: center; + gap: 3vw; + padding: 12px; + border-radius: 12px; + background: #fff; +} + +.modal-title { + color: rgba(0, 0, 0, 0.8); + font-size: 1.2em; + font-weight: 800; +} + +.modal-message { + color: rgba(0, 0, 0, 0.42); + font-size: 1em; + font-weight: 700; + text-align: center; +} + +.modal-actions { + width: 80%; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-primary, +.modal-secondary { + width: 45%; + min-height: 38px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 70px; + padding: 8px 12px; + font-weight: 800; +} + +.modal-secondary { + border: 1px solid #ffb805; + background: #fff; + color: #ff9500; +} + +.modal-primary { + background: #ff9500; + color: #fff; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 30; + max-width: calc(100% - 40px); + transform: translateX(-50%); + padding: 9px 13px; + border-radius: 999px; + background: rgba(22, 56, 36, 0.92); + color: #fff; + font-size: 0.9em; + font-weight: 700; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .details-button { + flex-direction: row-reverse; +} + +[dir="rtl"] .details-button img { + margin-left: 0; + margin-right: 4px; + transform: scaleX(-1); +} + +@media screen and (max-width: 360px) { + * { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + * { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + * { + font-size: 24px; + } +} diff --git a/h5/exchange-gold-coins/exchange-gold-coins.js b/h5/exchange-gold-coins/exchange-gold-coins.js new file mode 100644 index 0000000..c84cc7d --- /dev/null +++ b/h5/exchange-gold-coins/exchange-gold-coins.js @@ -0,0 +1,442 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; +const icons = { + back: "../host-center/icons/back.svg", + arrow: "../host-center/icons/arrow.svg", + coin: "./icons/coin.svg" +}; + +let commonHeaders = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai" +}; + +const coinOptions = [ + { 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 i18n = { + en: { + exchange_gold_coins: "Exchange Coins", + information: "Information", + details: "Details", + available_salaries: "Available salaries", + loading: "Loading", + exchange: "Exchange", + tips: "Tips", + confirm_exchange: "Confirm exchange?", + cancel: "Cancel", + confirm: "Confirm", + failed_to_load_balance: "Failed to load balance.", + exchange_not_available: "Exchange is not available.", + request_not_acceptable: "Request not acceptable.", + failed_to_load_user_info: "Failed to load user info.", + please_select_coin_amount: "Please select coin amount.", + insufficient_balance: "Insufficient balance. Balance: {balance}, price: {price}.", + exchange_success: "Exchange success: ${price}.", + exchange_failed: "Exchange failed.", + exchange_error_occurred: "Exchange error occurred." + }, + ar: { + exchange_gold_coins: "استبدال العملات", + information: "معلومات", + details: "التفاصيل", + available_salaries: "الرواتب المتاحة", + loading: "جار التحميل", + exchange: "استبدال", + tips: "نصائح", + confirm_exchange: "تأكيد الاستبدال؟", + cancel: "إلغاء", + confirm: "تأكيد", + failed_to_load_balance: "فشل تحميل الرصيد.", + exchange_not_available: "الاستبدال غير متاح.", + request_not_acceptable: "الطلب غير مقبول.", + failed_to_load_user_info: "فشل تحميل معلومات المستخدم.", + please_select_coin_amount: "يرجى اختيار كمية العملات.", + insufficient_balance: "الرصيد غير كاف. الرصيد: {balance}، السعر: {price}.", + exchange_success: "تم الاستبدال بنجاح: ${price}.", + exchange_failed: "فشل الاستبدال.", + exchange_error_occurred: "حدث خطأ في الاستبدال." + }, + tr: { + exchange_gold_coins: "Coin Değiştir", + information: "Bilgi", + details: "Detaylar", + available_salaries: "Kullanılabilir maaşlar", + loading: "Yükleniyor", + exchange: "Değiştir", + tips: "İpuçları", + confirm_exchange: "Değişimi onaylıyor musunuz?", + cancel: "İptal", + confirm: "Onayla", + failed_to_load_balance: "Bakiye yüklenemedi.", + exchange_not_available: "Değişim kullanılamıyor.", + request_not_acceptable: "İstek kabul edilemez.", + failed_to_load_user_info: "Kullanıcı bilgisi yüklenemedi.", + please_select_coin_amount: "Lütfen coin miktarı seçin.", + insufficient_balance: "Yetersiz bakiye. Bakiye: {balance}, fiyat: {price}.", + exchange_success: "Değişim başarılı: ${price}.", + exchange_failed: "Değişim başarısız.", + exchange_error_occurred: "Değişim hatası oluştu." + }, + bn: { + exchange_gold_coins: "কয়েন এক্সচেঞ্জ", + information: "তথ্য", + details: "বিস্তারিত", + available_salaries: "উপলভ্য বেতন", + loading: "লোড হচ্ছে", + exchange: "এক্সচেঞ্জ", + tips: "টিপস", + confirm_exchange: "এক্সচেঞ্জ নিশ্চিত করবেন?", + cancel: "বাতিল", + confirm: "নিশ্চিত", + failed_to_load_balance: "ব্যালেন্স লোড ব্যর্থ।", + exchange_not_available: "এক্সচেঞ্জ উপলভ্য নয়।", + request_not_acceptable: "রিকোয়েস্ট গ্রহণযোগ্য নয়।", + failed_to_load_user_info: "ইউজার তথ্য লোড ব্যর্থ।", + please_select_coin_amount: "কয়েন পরিমাণ নির্বাচন করুন।", + insufficient_balance: "ব্যালেন্স অপর্যাপ্ত। ব্যালেন্স: {balance}, মূল্য: {price}।", + exchange_success: "এক্সচেঞ্জ সফল: ${price}।", + exchange_failed: "এক্সচেঞ্জ ব্যর্থ।", + exchange_error_occurred: "এক্সচেঞ্জ ত্রুটি ঘটেছে।" + } +}; + +const state = { + lang: resolveLanguage(), + balance: "0", + selectedId: null, + isBalanceLoading: false, + isUserLoading: false, + role: "" +}; + +const $ = (selector) => document.querySelector(selector); +const $$ = (selector) => Array.from(document.querySelectorAll(selector)); + +function translate(key, params = {}) { + let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; + Object.entries(params).forEach(([name, value]) => { + text = text.replace(`{${name}}`, value); + }); + return text; +} + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const lang = params.get("lang") || navigator.language || "en"; + if (lang.startsWith("ar")) return "ar"; + if (lang.startsWith("tr")) return "tr"; + if (lang.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAccessOrigin(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + reqLang: data["Req-Lang"], + authorization: data.Authorization, + buildVersion: appIntel.build, + appVersion: appIntel.version, + appChannel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + sysOrigin: sysOrigin.origin, + sysOriginChild: sysOrigin.child + }; +} + +function setHeadersFromApp(headers) { + const next = {}; + if (headers.authorization) next.authorization = headers.authorization; + if (headers.reqLang) next["req-lang"] = headers.reqLang; + if (headers.appVersion || headers.buildVersion || headers.appChannel) { + const intel = []; + if (headers.appVersion) intel.push(`version=${headers.appVersion}`); + if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); + if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); + next["req-app-intel"] = intel.join(";"); + } + if (headers.reqImei) next["req-imei"] = headers.reqImei; + if (headers.sysOrigin) { + const origin = [`origin=${headers.sysOrigin}`]; + if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + next["req-sys-origin"] = origin.join(";"); + } + commonHeaders = { ...commonHeaders, ...next }; +} + +function connectToApp() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!(window.app || window.webkit || window.FlutterPageControl)) { + setHeadersFromApp({ + authorization: "", + reqLang: state.lang, + appVersion: "1.0.0", + buildVersion: "1", + appChannel: "Web", + reqImei: "H5-STATIC", + sysOrigin: "LIKEI", + sysOriginChild: "LIKEI" + }); + resolve(null); + return; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + resolve(window.app.getAccessOrigin()); + return; + } + if (attempt < 50) { + window.setTimeout(poll, 100); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + resolve(null); + }; + poll(); + }).then((raw) => { + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + }); +} + +async function apiGet(path) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: "GET", + headers: commonHeaders + }); + return parseApiResponse(response); +} + +async function apiPost(path, body) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: "POST", + headers: { + ...commonHeaders, + "content-type": "application/json" + }, + body: JSON.stringify(body || {}) + }); + return parseApiResponse(response); +} + +async function parseApiResponse(response) { + const data = await response.json().catch(() => ({})); + if (!response.ok || data.status === false) { + const error = new Error(data.errorMsg || data.message || `HTTP Error: ${response.status}`); + error.status = response.status; + error.response = data; + error.errorCode = data.errorCode; + throw error; + } + return data; +} + +function navigateTo(route) { + const url = new URL(route, window.location.origin); + const lang = new URLSearchParams(window.location.search).get("lang"); + if (lang && !url.searchParams.has("lang")) url.searchParams.set("lang", lang); + window.location.href = `${url.pathname}${url.search}${url.hash}`; +} + +function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +function showToast(message) { + const toast = $("#toast"); + toast.textContent = message || "Request failed"; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function showModal() { + $("#confirmModal").hidden = false; +} + +function hideModal() { + $("#confirmModal").hidden = true; +} + +async function fetchBalance() { + state.isBalanceLoading = true; + renderBalance(); + try { + const result = await apiGet("/wallet/bank/balance"); + state.balance = result.status && typeof result.body === "number" ? result.body.toFixed(2) : "0.00"; + } catch (error) { + console.error("Failed to fetch bank balance:", error); + state.balance = "0.00"; + showToast(translate("failed_to_load_balance")); + } finally { + state.isBalanceLoading = false; + renderBalance(); + } +} + +async function checkExchangeAvailable() { + state.isUserLoading = true; + try { + const result = await apiGet("/wallet/bank/check/exchange-gold"); + if (result.status && result.body) { + state.role = result.body.role || ""; + console.debug("用户角色:", state.role); + return; + } + state.role = ""; + closePage(); + } catch (error) { + if (error.errorCode === 1083) { + showToast(translate("exchange_not_available")); + } else if (error.message && error.message.includes("HTTP Error: 406")) { + showToast(translate("request_not_acceptable")); + } else { + showToast(translate("failed_to_load_user_info")); + } + closePage(); + state.role = ""; + } finally { + state.isUserLoading = false; + } +} + +async function confirmExchange() { + hideModal(); + if (!state.selectedId) { + showToast(translate("please_select_coin_amount")); + return; + } + + const option = coinOptions.find((item) => item.id === state.selectedId); + const price = option.price; + const balance = parseFloat(state.balance); + if (Number.isNaN(balance) || balance < price) { + showToast(translate("insufficient_balance", { balance: balance.toFixed(2), price })); + return; + } + + try { + const result = await apiPost("/wallet/bank/exchange/gold", { + coinId: option.id, + amount: option.price, + price: option.price + }); + if (result.status === true || result.errorCode === 0) { + showToast(translate("exchange_success", { price: option.price })); + await fetchBalance(); + state.selectedId = null; + renderCoins(); + } else { + showToast(result.message || translate("exchange_failed")); + } + } catch (error) { + console.error("Exchange error:", error); + showToast(translate("exchange_error_occurred")); + } +} + +function renderI18n() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + $(".lang-button").textContent = state.lang.toUpperCase(); + $$("[data-i18n]").forEach((node) => { + node.textContent = translate(node.dataset.i18n); + }); +} + +function renderIcons() { + $("#backIcon").src = icons.back; + $("#detailsArrowIcon").src = icons.arrow; +} + +function renderBalance() { + $("#balanceLoading").hidden = !state.isBalanceLoading; + $("#balanceValue").hidden = state.isBalanceLoading; + $("#balanceValue").textContent = state.balance; +} + +function renderCoins() { + const grid = $("#coinGrid"); + grid.innerHTML = ""; + coinOptions.forEach((option) => { + const item = document.createElement("button"); + item.type = "button"; + item.className = `coin-item${state.selectedId === option.id ? " selected" : ""}`; + item.innerHTML = `
$${option.price}
`; + item.addEventListener("click", () => { + state.selectedId = state.selectedId === option.id ? null : option.id; + renderCoins(); + }); + grid.appendChild(item); + }); + $("#exchangeButton").disabled = !state.selectedId; +} + +function bindEvents() { + $("[data-action='back']").addEventListener("click", closePage); + $("[data-action='language']").addEventListener("click", () => navigateTo("/language")); + $$("[data-route]").forEach((node) => node.addEventListener("click", () => navigateTo(node.dataset.route))); + $("#exchangeButton").addEventListener("click", () => { + if (state.selectedId) showModal(); + }); + $("#confirmModal").addEventListener("click", hideModal); + $(".modal-card").addEventListener("click", (event) => event.stopPropagation()); + $("[data-action='cancel']").addEventListener("click", hideModal); + $("[data-action='confirm']").addEventListener("click", confirmExchange); +} + +async function init() { + renderI18n(); + renderIcons(); + renderBalance(); + renderCoins(); + bindEvents(); + await connectToApp(); + checkExchangeAvailable(); + fetchBalance(); + console.log("document.documentElement.getAttribute('dir') === 'rtl'", document.documentElement.getAttribute("dir") === "rtl"); +} + +init(); diff --git a/h5/exchange-gold-coins/icons/coin.svg b/h5/exchange-gold-coins/icons/coin.svg new file mode 100644 index 0000000..f5beff6 --- /dev/null +++ b/h5/exchange-gold-coins/icons/coin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/h5/exchange-gold-coins/index.html b/h5/exchange-gold-coins/index.html index dd21fa7..afc64ad 100644 --- a/h5/exchange-gold-coins/index.html +++ b/h5/exchange-gold-coins/index.html @@ -1,13 +1,61 @@ - - + + Exchange Coins - + - - +
+ + +
+
+
+
Information
+ +
+ +
+ Available salaries + + 0.00 +
+
+ +
+
Exchange Coins
+
+
+
+
+ +
+
+
+ + + + +
+ + diff --git a/h5/host-center/host-center.js b/h5/host-center/host-center.js index 8445100..d237f06 100644 --- a/h5/host-center/host-center.js +++ b/h5/host-center/host-center.js @@ -1,5 +1,5 @@ const API_BASE_URL = "https://jvapi.haiyihy.com"; -const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png"; +const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k.png"; const icons = { policy: "./icons/policy.svg", exchange: "./icons/exchange.svg", @@ -214,16 +214,6 @@ function connectToApp() { window.getIosAccessOriginParam = resolve; if (!(window.app || window.webkit || window.FlutterPageControl)) { - setHeadersFromApp({ - authorization: "", - reqLang: state.lang, - appVersion: "1.0.0", - buildVersion: "1", - appChannel: "Web", - reqImei: "H5-STATIC", - sysOrigin: "LIKEI", - sysOriginChild: "LIKEI" - }); resolve({ success: true, environment: "browser" }); return; } @@ -632,16 +622,16 @@ async function init() { await connectToApp(); await fetchUserProfile(); await Promise.all([ - runTask(checkIdentityAndRedirect, "identity check"), runTask(fetchTeamEntry, "team entry"), runTask(fetchAgencyProfile, "agency profile"), runTask(fetchBankBalance, "bank balance"), runTask(fetchWorkStatistics, "work statistics"), runTask(fetchBankCards, "bank cards"), runTask(fetchWithdrawInfo, "withdraw info"), - runTask(fetchWithdrawEnabled, "withdraw enabled"), - runTask(fetchMemberWork, "member work") + runTask(fetchWithdrawEnabled, "withdraw enabled") ]); + await checkIdentityAndRedirect(); + runTask(fetchMemberWork, "member work"); } catch (error) { console.error("Host center data request failed:", error); showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance")); diff --git a/h5/host-setting/host-setting.css b/h5/host-setting/host-setting.css new file mode 100644 index 0000000..ef780d9 --- /dev/null +++ b/h5/host-setting/host-setting.css @@ -0,0 +1,269 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.35; +} + +html, +body { + width: 100%; + min-height: 100%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; +} + +body { + color: rgba(0, 0, 0, 0.8); + background: #edf8f1; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.host-setting { + width: 100vw; + min-height: 100vh; + background: #edf8f1; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: #f8fff9; + border-bottom: 1px solid #cce9d7; + box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14); +} + +.page-header h1 { + min-width: 0; + color: #153d28; + font-size: 1.25em; + font-weight: 700; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.header-icon, +.lang-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: #1f6f46; + font-weight: 700; +} + +.header-icon { + padding: 6px; +} + +.header-icon img { + width: 100%; + height: 100%; + display: block; +} + +.lang-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + font-size: 0.86em; +} + +.page-content { + position: relative; + display: flex; + flex-direction: column; + gap: 2vw; + padding: 16px; +} + +.setting-section { + display: flex; + flex-direction: column; + gap: 2vw; +} + +.section-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.section-title, +.kyc-title { + display: flex; + align-items: center; + color: #173a28; + font-size: 1.4em; + font-weight: 700; +} + +.more-button { + display: flex; + align-items: center; + color: rgba(0, 0, 0, 0.42); + font-weight: 500; +} + +.surface-card { + border: 1px solid #b7e0c6; + border-radius: 20px; + background: #fbfffc; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.empty-row { + width: 100%; + min-height: 54px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + color: rgba(0, 0, 0, 0.8); + font-weight: 600; +} + +.bank-detail, +.kyc-detail { + width: 100%; + display: flex; + flex-direction: column; + gap: 2vw; + padding: 12px; +} + +.field-label { + color: rgba(0, 0, 0, 0.8); + font-weight: 600; +} + +.field-value, +.field-text { + color: rgba(0, 0, 0, 0.42); + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.field-text { + font-size: 0.8em; + overflow-wrap: break-word; + white-space: normal; +} + +.status-text { + margin-left: 4px; + font-size: 0.9em; + font-weight: 700; +} + +.status-pass { + color: #2fb95d; +} + +.status-failed { + color: #e04444; +} + +.status-pending { + color: #d79a12; +} + +.icon-route { + width: 6vw; + min-width: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.arrow-icon { + display: block; + width: 6vw; + min-width: 24px; + max-width: 34px; +} + +.preview-list { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.preview-list img { + display: block; + width: 15vw; + aspect-ratio: 1 / 1; + object-fit: cover; + border-radius: 5vw; + background: #edf8f1; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 30; + max-width: calc(100% - 40px); + transform: translateX(-50%); + padding: 9px 13px; + border-radius: 999px; + background: rgba(22, 56, 36, 0.92); + color: #fff; + font-size: 0.9em; + font-weight: 700; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .status-text { + margin-right: 4px; + margin-left: 0; +} + +[dir="rtl"] .arrow-icon, +[dir="rtl"] .header-icon img { + transform: scaleX(-1); +} + +@media screen and (max-width: 360px) { + * { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + * { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + * { + font-size: 24px; + } +} diff --git a/h5/host-setting/host-setting.js b/h5/host-setting/host-setting.js new file mode 100644 index 0000000..7fe7b41 --- /dev/null +++ b/h5/host-setting/host-setting.js @@ -0,0 +1,452 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; +const icons = { + back: "./icons/back.svg", + arrow: "./icons/arrow.svg" +}; + +let commonHeaders = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai" +}; + +const i18n = { + en: { + host_setting: "Host Setting", + bank_card: "Bank card", + more: "More", + add_payment_method: "Add payment method", + category: "Category", + card_number_label: "Card number", + payee: "Payee", + kyc: "KYC", + review_passed: "Review passed", + review_failed: "Review failed", + awaiting_review: "Awaiting review", + complete_information: "Complete information", + passport_id_card_title: "Passport / ID card", + contact_number: "Contact number", + other_description: "Other description", + something_went_wrong: "Something went wrong." + }, + ar: { + host_setting: "إعدادات المضيف", + bank_card: "البطاقة البنكية", + more: "المزيد", + add_payment_method: "إضافة طريقة دفع", + category: "الفئة", + card_number_label: "رقم البطاقة", + payee: "المستفيد", + kyc: "KYC", + review_passed: "تمت الموافقة", + review_failed: "فشل التحقق", + awaiting_review: "بانتظار المراجعة", + complete_information: "إكمال المعلومات", + passport_id_card_title: "جواز السفر / الهوية", + contact_number: "رقم التواصل", + other_description: "وصف آخر", + something_went_wrong: "حدث خطأ ما." + }, + tr: { + host_setting: "Host Ayarı", + bank_card: "Banka kartı", + more: "Daha fazla", + add_payment_method: "Ödeme yöntemi ekle", + category: "Kategori", + card_number_label: "Kart numarası", + payee: "Alacaklı", + kyc: "KYC", + review_passed: "İnceleme geçti", + review_failed: "İnceleme başarısız", + awaiting_review: "İnceleme bekliyor", + complete_information: "Bilgileri tamamla", + passport_id_card_title: "Pasaport / kimlik kartı", + contact_number: "İletişim numarası", + other_description: "Diğer açıklama", + something_went_wrong: "Bir şeyler ters gitti." + }, + bn: { + host_setting: "হোস্ট সেটিং", + bank_card: "ব্যাংক কার্ড", + more: "আরও", + add_payment_method: "পেমেন্ট পদ্ধতি যোগ করুন", + category: "ক্যাটাগরি", + card_number_label: "কার্ড নম্বর", + payee: "প্রাপক", + kyc: "KYC", + review_passed: "রিভিউ পাস হয়েছে", + review_failed: "রিভিউ ব্যর্থ হয়েছে", + awaiting_review: "রিভিউ অপেক্ষমাণ", + complete_information: "তথ্য সম্পূর্ণ করুন", + passport_id_card_title: "পাসপোর্ট / আইডি কার্ড", + contact_number: "যোগাযোগ নম্বর", + other_description: "অন্যান্য বিবরণ", + something_went_wrong: "কিছু ভুল হয়েছে।" + } +}; + +const state = { + lang: resolveLanguage(), + bankCard: null, + withdrawInfo: null, + profile: {} +}; + +const $ = (selector) => document.querySelector(selector); +const $$ = (selector) => Array.from(document.querySelectorAll(selector)); +const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : ""; + const hashParams = new URLSearchParams(hashQuery); + const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en"; + if (lang.startsWith("ar")) return "ar"; + if (lang.startsWith("tr")) return "tr"; + if (lang.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAccessOrigin(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + reqLang: data["Req-Lang"], + authorization: data.Authorization, + buildVersion: appIntel.build, + appVersion: appIntel.version, + appChannel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + sysOrigin: sysOrigin.origin, + sysOriginChild: sysOrigin.child + }; +} + +function setHeadersFromApp(headers) { + const next = {}; + if (headers.authorization) next.authorization = headers.authorization; + if (headers.reqLang) next["req-lang"] = headers.reqLang; + if (headers.appVersion || headers.buildVersion || headers.appChannel) { + const intel = []; + if (headers.appVersion) intel.push(`version=${headers.appVersion}`); + if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); + if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); + next["req-app-intel"] = intel.join(";"); + } + if (headers.reqImei) next["req-imei"] = headers.reqImei; + if (headers.sysOrigin) { + const origin = [`origin=${headers.sysOrigin}`]; + if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + next["req-sys-origin"] = origin.join(";"); + } + commonHeaders = { ...commonHeaders, ...next }; +} + +function connectToApp() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!(window.app || window.webkit || window.FlutterPageControl)) { + resolve({ success: true, environment: "browser" }); + return; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + const raw = window.app.getAccessOrigin(); + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + resolve({ success: true, environment: "app" }); + return; + } + if (attempt < 50) { + window.setTimeout(poll, 100); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + resolve({ success: true, environment: "app" }); + }; + poll(); + }).then((result) => { + if (typeof result === "string") { + setHeadersFromApp(parseAccessOrigin(result)); + return { success: true, environment: "app" }; + } + return result; + }); +} + +async function apiGet(path) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: "GET", + headers: commonHeaders + }); + 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; +} + +async function fetchUserProfile() { + const cached = localStorage.getItem("userProfile"); + if (cached) { + try { + state.profile = JSON.parse(cached) || {}; + } catch (error) { + console.error("Failed to parse userProfile:", error); + } + } + try { + const result = await apiGet("/team/member/profile"); + if (result.status && result.body) { + state.profile = result.body; + localStorage.setItem("userProfile", JSON.stringify(result.body)); + } + } catch (error) { + console.error("Failed to fetch user profile:", error); + } +} + +function getMemberId() { + return state.profile.id || state.profile.userId || state.profile.account || null; +} + +function calculateRoles(identity) { + if (!identity) return ["guest"]; + const roles = []; + if (identity.freightAgent) roles.push("freightAgent"); + if (identity.agent) roles.push("agent"); + if (identity.bdLeader) roles.push("bdLeader"); + if (identity.bd) roles.push("bd"); + if (identity.anchor) roles.push("anchor"); + if (identity.admin) roles.push("admin"); + return roles.length ? roles : ["guest"]; +} + +function getPrimaryRole(roles) { + const weight = { + guest: 0, + anchor: 1, + agent: 2, + bd: 3, + bdLeader: 4, + freightAgent: 5, + admin: 6 + }; + return roles.reduce((current, role) => (weight[role] > weight[current] ? role : current), "guest"); +} + +function getDefaultPage(role) { + return { + admin: "/admin-center", + freightAgent: "/coin-seller", + agent: "/agency-center", + bdLeader: "/bd-leader-center", + bd: "/bd-center", + anchor: "/host-center", + guest: "/apply" + }[role] || "/apply"; +} + +async function checkIdentityAndRedirect() { + const memberId = getMemberId(); + if (!memberId) { + navigateTo("/apply"); + return; + } + const result = await apiGet("/app/h5/identity"); + if (!result.status || !result.body) return; + const roles = calculateRoles(result.body); + if (!roles.includes("anchor")) { + navigateTo(getDefaultPage(getPrimaryRole(roles))); + } +} + +async function fetchBankCards() { + const result = await apiGet("/wallet/bank-card/list"); + if (result.status && result.body) { + state.bankCard = result.body.find((item) => item.use) || null; + } +} + +async function fetchWithdrawInfo() { + const result = await apiGet("/wallet/withdraw-info/list"); + if (result.status && result.body && result.body.length > 0) { + const first = result.body[0]; + let previewUrls = []; + try { + previewUrls = JSON.parse(first.passportFrontUrl || "[]").map((item) => item); + } catch (error) { + console.error("Failed to parse passportFrontUrl:", error); + } + state.withdrawInfo = { previewUrls, ...first }; + } +} + +function normalizeRoute(target) { + const url = new URL(target, window.location.origin); + const currentLang = new URLSearchParams(window.location.search).get("lang"); + if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang); + return `${url.pathname}${url.search}${url.hash}`; +} + +function navigateTo(target) { + window.location.href = normalizeRoute(target); +} + +function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +function showToast(message) { + const toast = $("#toast"); + toast.textContent = message; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function renderI18n() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + $("[data-action='language']").textContent = state.lang.toUpperCase(); + $$("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); +} + +function renderIcons() { + $("#backIcon").src = icons.back; + ["#bankAddArrow", "#kycHeaderArrow", "#kycAddArrow"].forEach((selector) => { + $(selector).src = icons.arrow; + }); +} + +function renderBankCard() { + const hasCard = Boolean(state.bankCard); + $("#bankMoreButton").hidden = !hasCard; + $("#addBankCardRow").hidden = hasCard; + $("#bankDetail").hidden = !hasCard; + if (!hasCard) return; + $("#cardType").textContent = state.bankCard.cardType || "-"; + $("#cardNo").textContent = state.bankCard.cardNo || "-"; + $("#payee").textContent = state.bankCard.payee || "-"; +} + +function renderKyc() { + const info = state.withdrawInfo; + const hasInfo = Boolean(info); + $("#kycColon").hidden = !hasInfo; + $("#kycStatus").hidden = !hasInfo; + $("#kycHeaderButton").hidden = !hasInfo; + $("#completeKycRow").hidden = hasInfo; + $("#kycDetail").hidden = !hasInfo; + if (!hasInfo) return; + + const status = $("#kycStatus"); + status.className = "status-text"; + if (info.status === "PASS") { + status.textContent = t("review_passed"); + status.classList.add("status-pass"); + } else if (info.status === "NOT_PASS") { + status.textContent = t("review_failed"); + status.classList.add("status-failed"); + } else if (info.status === "PENDING") { + status.textContent = t("awaiting_review"); + status.classList.add("status-pending"); + } else { + status.textContent = info.status || ""; + } + + const previewList = $("#previewList"); + previewList.innerHTML = ""; + (info.previewUrls || []).forEach((url) => { + const image = document.createElement("img"); + image.src = url; + image.alt = ""; + previewList.appendChild(image); + }); + $("#contactNumber").textContent = info.contactNumber || "-"; + $("#otherDescription").textContent = info.otherDescription || "-"; +} + +function render() { + renderI18n(); + renderIcons(); + renderBankCard(); + renderKyc(); +} + +function bindEvents() { + $("[data-action='back']").addEventListener("click", closePage); + $("[data-action='language']").addEventListener("click", () => navigateTo("/language")); + $$("[data-route]").forEach((node) => { + node.addEventListener("click", () => navigateTo(node.dataset.route)); + }); +} + +async function runTask(task, label) { + try { + await task(); + } catch (error) { + console.error(`${label} failed:`, error); + if (label === "withdraw info") showToast(error.response?.errorMsg || error.message || t("something_went_wrong")); + } +} + +async function init() { + localStorage.setItem("identity", JSON.stringify({ identity: "HOST" })); + bindEvents(); + render(); + try { + await connectToApp(); + await fetchUserProfile(); + await runTask(checkIdentityAndRedirect, "identity check"); + await Promise.all([ + runTask(fetchBankCards, "bank cards"), + runTask(fetchWithdrawInfo, "withdraw info") + ]); + } catch (error) { + console.error("Host setting initialization failed:", error); + showToast(error.response?.errorMsg || error.message || t("something_went_wrong")); + } finally { + render(); + } +} + +init(); diff --git a/h5/host-setting/icons/arrow.svg b/h5/host-setting/icons/arrow.svg new file mode 100644 index 0000000..7e4862b --- /dev/null +++ b/h5/host-setting/icons/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/host-setting/icons/back.svg b/h5/host-setting/icons/back.svg new file mode 100644 index 0000000..b74cc76 --- /dev/null +++ b/h5/host-setting/icons/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/host-setting/index.html b/h5/host-setting/index.html index d1ec1ff..2a4f53a 100644 --- a/h5/host-setting/index.html +++ b/h5/host-setting/index.html @@ -1,13 +1,70 @@ - - + + Host Setting - + - - +
+ + +
+
+
+
Bank card
+ +
+
+ + +
+
+ +
+
+
+ KYC + + +
+ +
+
+ + +
+
+
+ + +
+ + diff --git a/h5/invite-agency/index.html b/h5/invite-agency/index.html index 04b88bc..8d12d9c 100644 --- a/h5/invite-agency/index.html +++ b/h5/invite-agency/index.html @@ -1,110 +1,28 @@ - + - - - Invite User To Become Agent - + + + Invite Agent + -
-
-
-
- -
-

Invite User To Become Agent

-
-
-
English
-
-
-
- -
-
-
-
-
- - -
+
+
+
+
+
+ + +
- +
- -
-
please input user id
-
-
- - - - +
+
No users found
+ + + diff --git a/h5/invite-agency/invite.js b/h5/invite-agency/invite.js new file mode 100644 index 0000000..e86544c --- /dev/null +++ b/h5/invite-agency/invite.js @@ -0,0 +1,179 @@ +import { applyLang, avatarError, bindHeader, connectToApp, get, post, t, toast } from "../bd-static/shared.js"; + +const state = { + query: "", + invited: [], + searched: [], + selected: null, + showSearch: false, + locked: false +}; + +const input = document.getElementById("searchInput"); +const clearButton = document.getElementById("clearButton"); +const inviteButton = document.getElementById("inviteButton"); +const list = document.getElementById("list"); +const empty = document.getElementById("empty"); + +bindHeader("invite_become_agent"); +applyLang(); +input.placeholder = t("enter_user_id"); + +input.addEventListener("input", () => { + state.query = input.value; + clearButton.hidden = !state.query; + state.showSearch = false; + state.selected = null; + inviteButton.disabled = true; + if (!state.query.trim()) render(); +}); + +input.addEventListener("keyup", (event) => { + if (event.key === "Enter") search(); +}); + +clearButton.addEventListener("click", () => { + state.query = ""; + state.searched = []; + state.selected = null; + state.showSearch = false; + input.value = ""; + clearButton.hidden = true; + inviteButton.disabled = true; + render(); +}); + +inviteButton.addEventListener("click", inviteSelected); + +let searchTimer = 0; +input.addEventListener("input", () => { + window.clearTimeout(searchTimer); + if (state.query.trim()) searchTimer = window.setTimeout(search, 500); +}); + +async function init() { + try { + await connectToApp(); + await loadInvited(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +async function loadInvited() { + const response = await get("/team/bd/invite-message-own"); + state.invited = response.status && Array.isArray(response.body) ? response.body : []; + render(); +} + +async function search() { + if (state.locked) return; + const value = state.query.trim(); + state.selected = null; + inviteButton.disabled = true; + if (!value) { + state.showSearch = false; + state.searched = []; + render(); + return; + } + state.locked = true; + state.showSearch = true; + state.searched = []; + render(); + try { + const response = await get(`/user/user-profile/open-search?account=${encodeURIComponent(value)}`); + state.searched = response.status && response.body ? [{ userProfile: response.body }] : []; + } catch (error) { + toast(error.response?.errorMsg || error.message); + } finally { + state.locked = false; + render(); + } +} + +function selectItem(item) { + const profile = item.userProfile || {}; + if (state.selected?.id === profile.id) { + state.selected = null; + inviteButton.disabled = true; + render(); + return; + } + state.selected = profile; + inviteButton.disabled = item.status === 0 || item.status === 1; + render(); +} + +async function inviteSelected() { + if (!state.selected?.id) return; + try { + const response = await post("/team/bd/invite-agent", { inviteUserId: state.selected.id }); + if (response.status) toast(t("invitation_submitted")); + } catch (error) { + toast(error.response?.errorMsg || t("something_went_wrong")); + } + state.query = ""; + state.searched = []; + state.selected = null; + state.showSearch = false; + input.value = ""; + clearButton.hidden = true; + inviteButton.disabled = true; + await loadInvited(); +} + +async function cancelInvitation(id) { + try { + const response = await post(`/team/bd/invite-agent-cancel/${id}`); + if (response.status) { + toast(t("application_cancelled")); + await loadInvited(); + } + } catch (error) { + const message = error.response?.errorMsg?.replace(/^.*\]\s*/, "") || t("cancellation_failed_try_again"); + toast(message); + } +} + +function render() { + const rows = state.showSearch ? state.searched : state.invited; + list.innerHTML = ""; + rows.forEach((item) => list.appendChild(card(item))); + empty.hidden = rows.length > 0; +} + +function card(item) { + const profile = item.userProfile || {}; + const node = document.createElement("article"); + node.className = "card card-pad"; + node.style.cssText = "position:relative;margin-top:12px"; + if (state.selected?.id === profile.id) node.style.outline = "2px solid #32a96b"; + node.innerHTML = ` + ${item.status === 0 ? `${t("pending")}` : ""} + ${item.status === 1 ? `${t("success")}` : ""} +
${t("information")}:
+
+
+ +
+
${profile.userNickname || "-"}
+
${t("user_id_prefix")}${profile.account || "-"}
+
+
+ ${item.status === 0 ? `` : ""} +
+ `; + avatarError(node.querySelector("img")); + node.addEventListener("click", () => selectItem(item)); + const cancel = node.querySelector("[data-cancel]"); + if (cancel) { + cancel.addEventListener("click", (event) => { + event.stopPropagation(); + cancelInvitation(item.id); + }); + } + return node; +} + +init(); diff --git a/h5/invite-agency/yumi-invite-agency.css b/h5/invite-agency/yumi-invite-agency.css deleted file mode 100644 index 828a8df..0000000 --- a/h5/invite-agency/yumi-invite-agency.css +++ /dev/null @@ -1,177 +0,0 @@ -html { - -webkit-text-size-adjust: 100%; - -webkit-overflow-scrolling: touch; -} - -body { - margin: 0; - min-height: 100%; - font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif; - background: #edf8f1; - color: #1f3528; -} - -* { box-sizing: border-box; } -button, input { font: inherit; } -[hidden] { display: none !important; } - -.fullPage { - width: 100vw; - min-height: 100vh; - background: #edf8f1; - position: relative; -} - -.fullPage input::placeholder { - font-weight: 700; - color: #7b8d81; -} - -.invite-btn, .result-action-btn { - padding: 7px 14px; - border-radius: 32px; - background: #dff5e7; - color: #1f6f46; - border: 1px solid #b7e0c6; - font-weight: 800; - cursor: pointer; - transition: transform 0.2s, background 0.2s; - white-space: nowrap; -} - -.invite-btn:disabled, .result-action-btn:disabled { - background: #e1e7e3; - border-color: #ccd6d0; - color: #7b8d81; - cursor: not-allowed; -} - -.no-data { - text-align: center; - padding: 40px 20px; - color: #6d7e72; - font-weight: 700; -} - -.user-card { cursor: default; } - -.status-bar-placeholder { display: none; height: 30px; } -.status-bar-placeholder.is-visible { display: block; } - -.header-container { - position: sticky; - top: 0; - z-index: 10; - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - height: 60px; - background: #f8fff9; - border-bottom: 1px solid #cce9d7; - box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14); - font-size: 16px; -} - -.side-area, .actions-area { - min-width: 1.5em; - display: flex; - align-items: center; -} - -.actions-area { - justify-content: flex-end; - gap: 8px; -} - -.header-title { - flex: 1; - margin: 0; - color: #153d28 !important; - font-size: 1.2em; - font-weight: 800; - text-align: center; - letter-spacing: 0; -} - -.back-btn, .help-btn { - width: 1.5em; - border: none; - background: transparent; - transition: transform 0.2s ease; - padding: 0; -} - -.back-btn:active, .help-btn:active { transform: scale(0.9); } - -.language-entry { - display: flex; - align-items: center; - border-radius: 999px; - background: #dff5e7; - padding: 5px 10px; - color: #24724a !important; - cursor: pointer; -} - -.language-name { font-weight: 800; } - -.surface-border-wrapper, .gradient-border-wrapper { - position: relative; - border: 1px solid #b7e0c6; - background: #ffffff; - box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); -} - -.surface-border-content, .gradient-border-content { - position: relative; - border-radius: inherit; - width: 100%; - height: 100%; - background: #fbfffc; -} - -.modal-overlay { - position: fixed; - inset: 0; - background-color: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; - padding: 20px; -} - -.modal { - background: #fff; - border-radius: 16px; - box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04); - width: 100%; - max-width: 320px; - padding: 24px 20px 20px; - text-align: center; - position: relative; -} - -.modal-title { - font-size: 18px; - font-weight: 700; - color: #1f3528; - margin-bottom: 12px; -} - -.modal-message { - font-size: 14px; - color: #6b7280; - line-height: 1.5; - margin-bottom: 20px; -} - -.modal-button { - border: none; - border-radius: 12px; - background: #dff5e7; - color: #1f6f46; - padding: 10px 16px; - font-weight: 800; -} diff --git a/h5/invite-agency/yumi-invite-agency.js b/h5/invite-agency/yumi-invite-agency.js deleted file mode 100644 index 744df40..0000000 --- a/h5/invite-agency/yumi-invite-agency.js +++ /dev/null @@ -1,694 +0,0 @@ -(function () { - const API_BASE = "https://jvapi.haiyihy.com"; - const BACK_ICON = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; - - const TEXT = { - pageTitle: "Invite User To Become Agent", - language: "English", - placeholder: "Enter User ID", - search: "Search", - invite: "Invite", - idlePrompt: "please input user id", - noUsersFound: "No users found", - information: "Information:", - userIdPrefix: "User ID:", - pending: "Pending", - success: "Success", - cancel: "Cancel", - invitationSubmitted: "Invitation submitted", - applicationCancelled: "Application cancelled", - somethingWentWrong: "Something went wrong", - cancellationFailed: "Cancellation failed, try again", - informationTitle: "Information", - successTitle: "Success", - errorTitle: "Error", - ok: "OK", - }; - - const state = { - query: "", - invitedList: [], - searchResults: [], - showSearchResults: false, - searching: false, - hasSearched: false, - }; - - const dom = { - statusBarPlaceholder: document.getElementById("statusBarPlaceholder"), - backButton: document.getElementById("backButton"), - backButtonIcon: document.getElementById("backButtonIcon"), - pageTitle: document.getElementById("pageTitle"), - languageEntry: document.getElementById("languageEntry"), - languageName: document.getElementById("languageName"), - searchInput: document.getElementById("searchInput"), - clearButton: document.getElementById("clearButton"), - searchButton: document.getElementById("searchButton"), - resultList: document.getElementById("resultList"), - noData: document.getElementById("noData"), - userCardTemplate: document.getElementById("userCardTemplate"), - }; - - init(); - - function init() { - document.title = TEXT.pageTitle; - document.documentElement.lang = "en"; - document.documentElement.dir = resolveDocumentDirection(); - - dom.backButtonIcon.src = BACK_ICON; - dom.pageTitle.textContent = TEXT.pageTitle; - dom.languageName.textContent = TEXT.language; - dom.searchInput.placeholder = TEXT.placeholder; - dom.searchButton.textContent = TEXT.search; - dom.noData.textContent = TEXT.idlePrompt; - - syncStatusBarPlaceholder(); - bindEvents(); - render(); - loadInvitedList(); - } - - function bindEvents() { - dom.backButton.addEventListener("click", handleBack); - dom.languageEntry.addEventListener("click", handleLanguageClick); - dom.searchInput.addEventListener("input", handleSearchInput); - dom.clearButton.addEventListener("click", clearSearch); - dom.searchButton.addEventListener("click", triggerSearchNow); - } - - function resolveDocumentDirection() { - const lang = (new URLSearchParams(window.location.search).get("lang") || "").toLowerCase(); - return lang.startsWith("ar") ? "rtl" : "ltr"; - } - - function syncStatusBarPlaceholder() { - const hasAppBridge = - Boolean(window.app) || - Boolean(window.FlutterPageControl) || - Boolean(window.webkit && window.webkit.messageHandlers); - const isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent || ""); - dom.statusBarPlaceholder.classList.toggle("is-visible", hasAppBridge && isIOS); - } - - function handleBack() { - if (window.history.length > 1) { - window.history.back(); - return; - } - - window.location.href = "/"; - } - - function handleLanguageClick() { - window.location.href = "/language"; - } - - function handleSearchInput(event) { - state.query = event.target.value; - dom.clearButton.hidden = !state.query; - state.showSearchResults = false; - state.hasSearched = false; - render(); - } - - function clearSearch() { - state.query = ""; - state.searchResults = []; - state.showSearchResults = false; - state.hasSearched = false; - dom.searchInput.value = ""; - dom.clearButton.hidden = true; - render(); - } - - async function triggerSearchNow() { - if (state.searching) { - return; - } - - const query = state.query.trim(); - - if (!query) { - state.showSearchResults = false; - state.hasSearched = false; - render(); - return; - } - - state.searching = true; - state.showSearchResults = true; - state.hasSearched = true; - state.searchResults = []; - render(); - - try { - const payload = await searchUser(query); - state.searchResults = payload ? [mergeWithInvitationStatus(payload.userProfile)] : []; - } catch (error) { - state.searchResults = []; - showToast(extractErrorMessage(error), { - title: TEXT.errorTitle, - type: "error", - }); - } finally { - state.searching = false; - render(); - } - } - - async function loadInvitedList() { - try { - const response = await request("GET", "/team/bd/invite-message-own"); - state.invitedList = response && response.status && Array.isArray(response.body) ? response.body : []; - state.searchResults = state.searchResults.map(function (item) { - return mergeWithInvitationStatus(item.userProfile || {}); - }); - render(); - } catch (error) { - state.invitedList = []; - render(); - } - } - - async function searchUser(query) { - try { - const response = await request( - "GET", - "/user/user-profile/open-search?account=" + encodeURIComponent(query) - ); - - if (response && response.status && response.body) { - return { userProfile: response.body }; - } - } catch (error) { - if (!/^\d+$/.test(query)) { - throw error; - } - } - - if (!/^\d+$/.test(query)) { - return null; - } - - const fallbackResponse = await request( - "GET", - "/user/user-profile?userId=" + encodeURIComponent(query) - ); - - if (fallbackResponse && fallbackResponse.status && fallbackResponse.body) { - return { userProfile: fallbackResponse.body }; - } - - return null; - } - - function mergeWithInvitationStatus(userProfile) { - const matchedInvitation = state.invitedList.find(function (item) { - return ( - item && - item.userProfile && - userProfile && - item.userProfile.id === userProfile.id - ); - }); - - return Object.assign({}, matchedInvitation || {}, { - userProfile: userProfile || {}, - }); - } - - async function inviteUser(item) { - if (!item || !item.userProfile || !item.userProfile.id) { - return; - } - - try { - const response = await request("POST", "/team/bd/invite-agent", { - body: { - inviteUserId: item.userProfile.id, - }, - }); - - if (response && response.status) { - showToast(TEXT.invitationSubmitted, { - title: TEXT.successTitle, - type: "success", - }); - await loadInvitedList(); - } - } catch (error) { - showToast(extractErrorMessage(error, TEXT.somethingWentWrong), { - title: TEXT.errorTitle, - type: "error", - }); - } - } - - async function cancelInvitation(invitationId) { - try { - const response = await request("POST", "/team/bd/invite-agent-cancel/" + invitationId); - - if (response && response.status) { - showToast(TEXT.applicationCancelled, { - title: TEXT.informationTitle, - type: "info", - }); - await loadInvitedList(); - } - } catch (error) { - showToast(extractErrorMessage(error, TEXT.cancellationFailed), { - title: TEXT.errorTitle, - type: "error", - }); - } - } - - function render() { - renderSearchButton(); - renderList(); - } - - function renderSearchButton() { - dom.searchButton.disabled = !state.query.trim() || state.searching; - } - - function renderList() { - const currentList = state.showSearchResults ? state.searchResults : state.invitedList; - dom.resultList.innerHTML = ""; - - currentList.forEach(function (item) { - dom.resultList.appendChild(createUserCard(item, state.showSearchResults ? "search" : "invited")); - }); - - if (currentList.length > 0 || (state.searching && state.showSearchResults)) { - dom.noData.hidden = true; - return; - } - - dom.noData.hidden = false; - dom.noData.textContent = - state.showSearchResults && state.hasSearched ? TEXT.noUsersFound : TEXT.idlePrompt; - } - - function createUserCard(item, mode) { - const fragment = dom.userCardTemplate.content.cloneNode(true); - const statusBadge = fragment.querySelector(".status-badge"); - const avatarImage = fragment.querySelector(".avatar-image"); - const nickname = fragment.querySelector(".nickname"); - const account = fragment.querySelector(".account"); - const pendingActions = fragment.querySelector(".pending-actions"); - const userProfile = item.userProfile || {}; - - avatarImage.src = userProfile.userAvatar || DEFAULT_AVATAR; - avatarImage.alt = userProfile.userNickname || ""; - avatarImage.onerror = handleAvatarError; - nickname.textContent = userProfile.userNickname || ""; - account.textContent = TEXT.userIdPrefix + " " + (userProfile.account || userProfile.id || ""); - - if (item.status === 0) { - statusBadge.innerHTML = - '
' + - TEXT.pending + - "
"; - } else if (item.status === 1) { - statusBadge.innerHTML = - '
' + - TEXT.success + - "
"; - } - - if (mode === "search") { - const inviteButton = document.createElement("button"); - inviteButton.type = "button"; - inviteButton.className = "result-action-btn"; - inviteButton.textContent = TEXT.invite; - inviteButton.disabled = item.status === 0 || item.status === 1; - inviteButton.addEventListener("click", function (event) { - event.stopPropagation(); - inviteUser(item); - }); - pendingActions.appendChild(inviteButton); - } else if (item.status === 0) { - const cancelButton = document.createElement("div"); - cancelButton.textContent = TEXT.cancel; - cancelButton.style.padding = "4px 8px"; - cancelButton.style.borderRadius = "32px"; - cancelButton.style.backgroundColor = "red"; - cancelButton.style.color = "white"; - cancelButton.style.fontWeight = "600"; - cancelButton.style.zIndex = "5"; - cancelButton.addEventListener("click", function (event) { - event.stopPropagation(); - cancelInvitation(item.id); - }); - pendingActions.appendChild(cancelButton); - } - - return fragment; - } - - function handleAvatarError(event) { - event.target.onerror = null; - event.target.src = DEFAULT_AVATAR; - } - - function extractErrorMessage(error, fallback) { - return ( - (error && - error.response && - (error.response.errorMsg || error.response.message || error.response.error)) || - (error && error.message) || - fallback || - TEXT.somethingWentWrong - ); - } - - function showToast(message, options) { - const config = Object.assign( - { - title: TEXT.informationTitle, - type: "info", - confirmText: TEXT.ok, - }, - options || {} - ); - - const iconMap = { - error: "✕", - warning: "⚠", - success: "✓", - info: "i", - simple: "", - }; - - const overlay = document.createElement("div"); - overlay.className = "modal-overlay"; - overlay.innerHTML = - '"; - - const modal = overlay.querySelector(".modal"); - const closeButton = overlay.querySelector(".modal-btn"); - const close = function () { - modal.classList.remove("modal-enter"); - modal.classList.add("modal-leave"); - window.setTimeout(function () { - overlay.remove(); - }, 200); - }; - - closeButton.addEventListener("click", close); - overlay.addEventListener("click", function (event) { - if (event.target === overlay) { - close(); - } - }); - - document.body.appendChild(overlay); - } - - function escapeHtml(value) { - return String(value) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - async function request(method, path, options) { - const config = options || {}; - const headers = buildRequestHeaders(Boolean(config.body), config.headers || {}); - const response = await fetch(API_BASE + path, { - method: method, - credentials: "include", - headers: headers, - body: config.body ? JSON.stringify(config.body) : undefined, - }); - const text = await response.text(); - let payload = {}; - - if (text) { - try { - payload = JSON.parse(text); - } catch (error) { - payload = { - status: response.ok, - body: text, - }; - } - } - - if (!response.ok || payload.status === false) { - const requestError = new Error( - payload.errorMsg || payload.message || response.statusText || TEXT.somethingWentWrong - ); - requestError.response = payload; - throw requestError; - } - - return payload; - } - - function buildRequestHeaders(hasBody, overrideHeaders) { - const discovered = discoverAppHeaders(); - const headers = Object.assign({}, overrideHeaders || {}); - - if (hasBody) { - headers["Content-Type"] = headers["Content-Type"] || "application/json"; - } - - headers.Accept = headers.Accept || "application/json, text/plain, */*"; - - if (discovered.authorization) { - headers.Authorization = headers.Authorization || discovered.authorization; - } - - if (discovered.reqLang) { - headers["Req-Lang"] = headers["Req-Lang"] || discovered.reqLang; - } - - if (discovered.appVersion) { - headers["App-Version"] = headers["App-Version"] || discovered.appVersion; - } - - if (discovered.buildVersion) { - headers["Build-Version"] = headers["Build-Version"] || discovered.buildVersion; - } - - if (discovered.appChannel) { - headers["App-Channel"] = headers["App-Channel"] || discovered.appChannel; - } - - if (discovered.reqImei) { - headers["Req-Imei"] = headers["Req-Imei"] || discovered.reqImei; - } - - if (discovered.sysOrigin) { - headers["Sys-Origin"] = headers["Sys-Origin"] || discovered.sysOrigin; - } - - if (discovered.sysOriginChild) { - headers["Sys-Origin-Child"] = - headers["Sys-Origin-Child"] || discovered.sysOriginChild; - } - - return headers; - } - - function discoverAppHeaders() { - const query = new URLSearchParams(window.location.search); - const collected = []; - - collectCandidate(collected, window.__APP_HEADERS__); - collectCandidate(collected, window.__HEADER_INFO__); - collectCandidate(collected, window.headerInfo); - collectCandidate(collected, parseHeaderObjectFromQuery(query)); - - collectFromStorage(collected, window.localStorage); - collectFromStorage(collected, window.sessionStorage); - - return collected.reduce(function (result, candidate) { - return Object.assign(result, candidate); - }, { - reqLang: query.get("lang") || document.documentElement.lang || "en", - }); - } - - function parseHeaderObjectFromQuery(query) { - const candidate = {}; - mapHeaderValue(candidate, "authorization", query.get("authorization")); - mapHeaderValue(candidate, "authorization", query.get("token")); - mapHeaderValue(candidate, "reqLang", query.get("lang")); - mapHeaderValue(candidate, "appVersion", query.get("appVersion")); - mapHeaderValue(candidate, "buildVersion", query.get("buildVersion")); - mapHeaderValue(candidate, "appChannel", query.get("appChannel")); - mapHeaderValue(candidate, "reqImei", query.get("reqImei")); - mapHeaderValue(candidate, "sysOrigin", query.get("sysOrigin")); - mapHeaderValue(candidate, "sysOriginChild", query.get("sysOriginChild")); - return candidate; - } - - function collectFromStorage(collected, storage) { - if (!storage) { - return; - } - - const preferredKeys = [ - "authorization", - "Authorization", - "token", - "accessToken", - "headerInfo", - "headersFromApp", - "appHeaders", - "persist:root", - "user", - "userInfo", - ]; - - preferredKeys.forEach(function (key) { - try { - collectCandidate(collected, storage.getItem(key)); - } catch (error) { - return; - } - }); - - for (let index = 0; index < storage.length; index += 1) { - try { - const key = storage.key(index); - collectCandidate(collected, storage.getItem(key)); - } catch (error) { - return; - } - } - } - - function collectCandidate(collected, value) { - const normalized = normalizeHeaderCandidate(value, 0); - if (normalized && Object.keys(normalized).length > 0) { - collected.push(normalized); - } - } - - function normalizeHeaderCandidate(value, depth) { - if (depth > 4 || value == null) { - return null; - } - - if (typeof value === "string") { - const trimmed = value.trim(); - - if (!trimmed) { - return null; - } - - if (trimmed[0] === "{" || trimmed[0] === "[") { - try { - return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1); - } catch (error) { - return null; - } - } - - if (/bearer\s+/i.test(trimmed)) { - return { - authorization: trimmed, - }; - } - - return null; - } - - if (Array.isArray(value)) { - return value.reduce(function (result, entry) { - const normalized = normalizeHeaderCandidate(entry, depth + 1); - return normalized ? Object.assign(result, normalized) : result; - }, {}); - } - - if (typeof value !== "object") { - return null; - } - - const normalized = {}; - const aliasMap = { - authorization: ["authorization", "Authorization", "token", "accessToken"], - reqLang: ["reqLang", "Req-Lang", "lang", "language"], - appVersion: ["appVersion", "App-Version"], - buildVersion: ["buildVersion", "Build-Version"], - appChannel: ["appChannel", "App-Channel"], - reqImei: ["reqImei", "Req-Imei"], - sysOrigin: ["sysOrigin", "Sys-Origin"], - sysOriginChild: ["sysOriginChild", "Sys-Origin-Child"], - }; - - Object.keys(aliasMap).forEach(function (targetKey) { - aliasMap[targetKey].forEach(function (sourceKey) { - mapHeaderValue(normalized, targetKey, value[sourceKey]); - }); - }); - - ["headerInfo", "headers", "auth", "data", "state"].forEach(function (nestedKey) { - const nestedValue = normalizeHeaderCandidate(value[nestedKey], depth + 1); - if (nestedValue) { - Object.assign(normalized, nestedValue); - } - }); - - Object.keys(value).forEach(function (key) { - if ( - key === "headerInfo" || - key === "headers" || - key === "auth" || - key === "data" || - key === "state" - ) { - return; - } - - if (typeof value[key] === "object") { - const nestedValue = normalizeHeaderCandidate(value[key], depth + 1); - if (nestedValue) { - Object.assign(normalized, nestedValue); - } - } - }); - - return normalized; - } - - function mapHeaderValue(target, key, value) { - if (value == null || value === "" || target[key]) { - return; - } - - target[key] = String(value); - } -})(); diff --git a/h5/invite-bd-leader/yumi-invite-bd-leader.js b/h5/invite-bd-leader/yumi-invite-bd-leader.js index e889b1c..12edf8a 100644 --- a/h5/invite-bd-leader/yumi-invite-bd-leader.js +++ b/h5/invite-bd-leader/yumi-invite-bd-leader.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-bdleader-message-own", invite: "/team/bd/invite-bdleader", diff --git a/h5/invite-bd/index.html b/h5/invite-bd/index.html index 4b32ca9..c402ba8 100644 --- a/h5/invite-bd/index.html +++ b/h5/invite-bd/index.html @@ -1,109 +1,24 @@ - + - - - Invite User To Become BD - + + + Invite BD + -
-
-
-
- -
-

Invite User To Become BD

-
-
-
English
+
+
+
+
+
+
-
- -
-
-
-
-
- - -
-
-
- -
- -
-
please input user id
-
-
- - - - +
+
No users found
+ + + diff --git a/h5/invite-bd/invite.js b/h5/invite-bd/invite.js new file mode 100644 index 0000000..d0ca13d --- /dev/null +++ b/h5/invite-bd/invite.js @@ -0,0 +1,118 @@ +import { applyLang, avatarError, bindHeader, connectToApp, get, post, t, toast } from "../bd-static/shared.js"; + +const config = { + title: "invite_become_BD", + list: "/team/bd/invite-bd-message-own", + invite: "/team/bd/invite-bd", + cancel: (id) => `/team/bd/invite-bd-cancel/${id}` +}; + +let invited = []; +let searchResult = []; +let selected = null; + +bindHeader(config.title); +applyLang(); +const input = document.getElementById("searchInput"); +input.placeholder = t("enter_user_id"); +input.addEventListener("input", () => { + selected = null; + render(); +}); +input.addEventListener("keyup", (event) => { + if (event.key === "Enter") search(); +}); +document.getElementById("inviteButton").addEventListener("click", invite); +connectToApp().then(loadInvited); + +async function loadInvited() { + try { + const response = await get(config.list); + invited = response.body || []; + render(); + } catch (error) { + toast(error.response?.errorMsg || error.message); + } +} + +async function search() { + const account = input.value.trim(); + selected = null; + searchResult = []; + if (!account) { + await loadInvited(); + return; + } + try { + const response = await get(`/user/user-profile/open-search?account=${encodeURIComponent(account)}`); + if (response.status && response.body) searchResult = [{ userProfile: response.body }]; + } catch (error) { + toast(error.response?.errorMsg || error.message); + } + render(); +} + +function render() { + const list = document.getElementById("list"); + const data = input.value.trim() ? searchResult : invited; + list.innerHTML = ""; + document.getElementById("empty").hidden = data.length > 0; + data.forEach((item) => { + const user = item.userProfile || {}; + const card = document.createElement("article"); + card.className = "member-card"; + card.innerHTML = ` + +
+
${user.userNickname || "-"}
+
${t("user_id_prefix")}${user.account || "-"}
+
+
+ `; + card.addEventListener("click", () => { + if (!input.value.trim() || item.status === 0 || item.status === 1) return; + selected = selected?.id === user.id ? null : user; + render(); + }); + const side = card.querySelector(".member-side"); + if (item.status === 0) { + side.innerHTML = `
${t("pending")}
`; + side.querySelector("button").addEventListener("click", (event) => { + event.stopPropagation(); + cancelInvite(item.id); + }); + } else if (item.status === 1) { + side.innerHTML = `${t("success")}`; + } else if (selected?.id === user.id) { + side.innerHTML = ``; + } + avatarError(card.querySelector("img")); + list.appendChild(card); + }); + document.getElementById("inviteButton").disabled = !selected; +} + +async function invite() { + if (!selected) return; + try { + await post(config.invite, { inviteUserId: selected.id }); + toast(t("invitation_submitted")); + input.value = ""; + selected = null; + searchResult = []; + await loadInvited(); + } catch (error) { + toast(error.response?.errorMsg || t("something_went_wrong")); + } +} + +async function cancelInvite(id) { + try { + await post(config.cancel(id)); + toast(t("application_cancelled")); + await loadInvited(); + } catch (error) { + const msg = error.response?.errorMsg?.replace(/^.*\]\s*/, "") || t("cancellation_failed_try_again"); + toast(msg); + } +} diff --git a/h5/invite-bd/yumi-invite-bd.js b/h5/invite-bd/yumi-invite-bd.js index bec1e7f..642f5f2 100644 --- a/h5/invite-bd/yumi-invite-bd.js +++ b/h5/invite-bd/yumi-invite-bd.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-bd-message-own", invite: "/team/bd/invite-bd", diff --git a/h5/invite-recharge-agency/yumi-invite-recharge-agency.js b/h5/invite-recharge-agency/yumi-invite-recharge-agency.js index f0092c1..0c777c7 100644 --- a/h5/invite-recharge-agency/yumi-invite-recharge-agency.js +++ b/h5/invite-recharge-agency/yumi-invite-recharge-agency.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-recharge-agent-message-own", invite: "/team/bd/invite-recharge-agent", diff --git a/h5/js/imageHandler-DstQGL3Z-1776148658686.js b/h5/js/imageHandler-DstQGL3Z-1776148658686.js index 9759819..0bb7b73 100644 --- a/h5/js/imageHandler-DstQGL3Z-1776148658686.js +++ b/h5/js/imageHandler-DstQGL3Z-1776148658686.js @@ -1 +1 @@ -const e=r=>{console.log("头像资源出错"),r.target.onerror=null,r.target.src=new URL("/assets/defaultAvatar-CdxWBK1k-1776148661448.png",import.meta.url).href},t=(r,a)=>{console.log("图片资源出错"),r.target.onerror=null,a=="GOLD"?r.target.src=new URL("/assets/coin-lz3Tp0nX-1776148661447.png",import.meta.url).href:a=="DOLLARS"&&(r.target.src=new URL("/assets/dollar-fhua8OJo-1776148661448.png",import.meta.url).href)};export{t as a,e as h}; +const e=r=>{console.log("头像资源出错"),r.target.onerror=null,r.target.src=new URL("/assets/defaultAvatar-CdxWBK1k.png",import.meta.url).href},t=(r,a)=>{console.log("图片资源出错"),r.target.onerror=null,a=="GOLD"?r.target.src=new URL("/assets/coin-lz3Tp0nX-1776148661447.png",import.meta.url).href:a=="DOLLARS"&&(r.target.src=new URL("/assets/dollar-fhua8OJo-1776148661448.png",import.meta.url).href)};export{t as a,e as h}; diff --git a/h5/logic-pages/app.js b/h5/logic-pages/app.js index fab9e98..28ceb26 100644 --- a/h5/logic-pages/app.js +++ b/h5/logic-pages/app.js @@ -365,7 +365,7 @@ async function renderMessage() { async function updateApply(id, status, node) { try { - await post("/team/user/apply/record/update", { id, status }); + await post("/team/process/user/apply", { id, status }); node.remove(); toast(status === "AGREE" ? "Approved" : "Rejected"); } catch (error) { diff --git a/h5/platform-policy/index.html b/h5/platform-policy/index.html index 396524e..8c6993c 100644 --- a/h5/platform-policy/index.html +++ b/h5/platform-policy/index.html @@ -1,13 +1,25 @@ - - + + Platform Policy - + - - +
+ + +
+ +
+ + diff --git a/h5/platform-policy/platform-policy.css b/h5/platform-policy/platform-policy.css new file mode 100644 index 0000000..f88c3ed --- /dev/null +++ b/h5/platform-policy/platform-policy.css @@ -0,0 +1,229 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.35; +} + +html, +body { + width: 100%; + min-height: 100%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; +} + +body { + color: rgba(0, 0, 0, 0.8); + background: #fff; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.platform-policy { + width: 100vw; + min-height: 100vh; + background-color: #fff; + background-image: url("/assets/secondBg-CSqvFWr0-1776148661447.png"); + background-size: cover; + background-position: top center; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 44px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: rgba(255, 255, 255, 0.96); +} + +.page-header h1 { + min-width: 0; + color: #111; + font-size: 1.25em; + font-weight: 700; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.back-button { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; +} + +.back-button svg { + width: 28px; + height: 28px; +} + +.back-button path { + fill: none; + stroke: #111; + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; +} + +.content { + position: relative; + z-index: 2; + padding: 16px; +} + +.policy-stack { + display: flex; + flex-direction: column; + gap: 16px; +} + +.policy-card { + position: relative; + padding: 16px 20vw 16px 16px; + border: 1px solid rgba(194, 217, 240, 0.82); + border-radius: 20px; + background: #fff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.level-badge { + position: absolute; + top: -1vw; + right: 2vw; + width: 11vw; + min-width: 48px; + max-width: 82px; + aspect-ratio: 1 / 1; + background-image: url("/assets/levelBg--Rsqxs_B-1776148661447.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 1.5em; + font-weight: 400; +} + +.policy-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 5px; +} + +.policy-cell { + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + text-align: center; +} + +.policy-cell.align-left { + width: max-content; +} + +.policy-cell.align-right { + align-items: flex-end; +} + +.label { + color: rgba(0, 0, 0, 0.8); + font-weight: 600; +} + +.value { + color: rgba(0, 0, 0, 0.8); + font-weight: 500; + overflow-wrap: anywhere; +} + +.empty-state { + min-height: 50vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + color: #666; + text-align: center; +} + +.empty-icon { + margin-bottom: 16px; + font-size: 48px; +} + +.empty-state p { + margin: 0; + font-size: 16px; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 30; + max-width: calc(100% - 40px); + transform: translateX(-50%); + padding: 9px 13px; + border-radius: 999px; + background: rgba(22, 56, 36, 0.92); + color: #fff; + font-size: 0.9em; + font-weight: 700; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .policy-grid { + direction: rtl; +} + +[dir="rtl"] .level-badge { + right: auto; + left: 2vw; +} + +[dir="rtl"] .policy-card { + padding: 16px 16px 16px 20vw; +} + +@media screen and (max-width: 360px) { + * { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + * { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + * { + font-size: 24px; + } +} diff --git a/h5/platform-policy/platform-policy.js b/h5/platform-policy/platform-policy.js new file mode 100644 index 0000000..3694dc7 --- /dev/null +++ b/h5/platform-policy/platform-policy.js @@ -0,0 +1,285 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; + +let commonHeaders = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai" +}; + +const i18n = { + en: { + platform_policy: "Platform Policy", + time: "Time", + hours: "hours", + target: "Target", + host_salary: "Host salary", + agent_salary: "Agent salary", + total_salary: "Total salary", + no_policy_data_available: "No policy data available" + }, + ar: { + platform_policy: "سياسة المنصة", + time: "الوقت", + hours: "ساعات", + target: "الهدف", + host_salary: "راتب المضيف", + agent_salary: "راتب الوكيل", + total_salary: "إجمالي الراتب", + no_policy_data_available: "لا توجد بيانات سياسة" + }, + tr: { + platform_policy: "Platform politikası", + time: "Süre", + hours: "saat", + target: "Hedef", + host_salary: "Host maaşı", + agent_salary: "Ajans maaşı", + total_salary: "Toplam maaş", + no_policy_data_available: "Politika verisi yok" + }, + bn: { + platform_policy: "প্ল্যাটফর্ম নীতি", + time: "সময়", + hours: "ঘণ্টা", + target: "টার্গেট", + host_salary: "হোস্ট বেতন", + agent_salary: "এজেন্ট বেতন", + total_salary: "মোট বেতন", + no_policy_data_available: "কোনো নীতি ডেটা নেই" + } +}; + +const state = { + lang: resolveLanguage(), + fromAgencyCenter: new URLSearchParams(window.location.search).get("from") === "agencycenter", + policies: [] +}; + +const $ = (selector) => document.querySelector(selector); +const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const lang = params.get("lang") || navigator.language || "en"; + if (lang.startsWith("ar")) return "ar"; + if (lang.startsWith("tr")) return "tr"; + if (lang.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAccessOrigin(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + reqLang: data["Req-Lang"], + authorization: data.Authorization, + buildVersion: appIntel.build, + appVersion: appIntel.version, + appChannel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + sysOrigin: sysOrigin.origin, + sysOriginChild: sysOrigin.child + }; +} + +function setHeadersFromApp(headers) { + const next = {}; + if (headers.authorization) next.authorization = headers.authorization; + if (headers.reqLang) next["req-lang"] = headers.reqLang; + if (headers.appVersion || headers.buildVersion || headers.appChannel) { + const intel = []; + if (headers.appVersion) intel.push(`version=${headers.appVersion}`); + if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); + if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); + next["req-app-intel"] = intel.join(";"); + } + if (headers.reqImei) next["req-imei"] = headers.reqImei; + if (headers.sysOrigin) { + const origin = [`origin=${headers.sysOrigin}`]; + if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + next["req-sys-origin"] = origin.join(";"); + } + commonHeaders = { ...commonHeaders, ...next }; +} + +function connectToApp() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!(window.app || window.webkit || window.FlutterPageControl)) { + setHeadersFromApp({ + authorization: "", + reqLang: state.lang, + appVersion: "1.0.0", + buildVersion: "1", + appChannel: "Web", + reqImei: "H5-STATIC", + sysOrigin: "LIKEI", + sysOriginChild: "LIKEI" + }); + resolve(null); + return; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + resolve(window.app.getAccessOrigin()); + return; + } + if (attempt < 50) { + window.setTimeout(poll, 100); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + resolve(null); + }; + poll(); + }).then((raw) => { + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + }); +} + +async function apiGet(path) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: "GET", + headers: commonHeaders + }); + 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 getTeamId() { + const cached = localStorage.getItem("teamInfo"); + if (cached) { + try { + const teamInfo = JSON.parse(cached); + if (teamInfo && teamInfo.id) return teamInfo.id; + } catch (error) { + console.error("Failed to parse teamInfo:", error); + } + } + return null; +} + +function formatTarget(value) { + if (!value) return "0"; + const number = parseFloat(value); + if (Number.isNaN(number)) return value; + if (number >= 1000000) return `${(number / 1000000).toFixed(1)}M`; + if (number >= 1000) return `${(number / 1000).toFixed(1)}K`; + return number.toString(); +} + +function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +function showToast(message) { + const toast = $("#toast"); + toast.textContent = message || "Request failed"; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function renderI18n() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + document.querySelectorAll("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); +} + +function policyCell(label, value, className = "") { + return `
+
${label}
+
${value}
+
`; +} + +function renderPolicies() { + const root = $("#policyList"); + if (!state.policies.length) { + root.innerHTML = `
📋

${t("no_policy_data_available")}

`; + return; + } + + root.innerHTML = `
${state.policies.map((item) => ` +
+
Lv.${item.level}
+
+ ${policyCell(`${t("time")} (${t("hours")})`, item.onlineTime || 0, "align-left")} + ${policyCell(t("target"), formatTarget(item.target))} + ${policyCell(t("host_salary"), `$${item.memberSalary || 0}`, "align-right")} + ${state.fromAgencyCenter ? policyCell(t("agent_salary"), `$${item.ownSalary || 0}`, "align-left") : ""} + ${state.fromAgencyCenter ? policyCell(t("total_salary"), `$${item.totalSalary || 0}`) : ""} +
+
+ `).join("")}
`; +} + +async function fetchPolicies() { + const teamId = getTeamId(); + const result = await apiGet(`/team/release/policy?id=${teamId}`); + if (result.status && result.body && result.body.policy) { + state.policies = result.body.policy || []; + } else { + state.policies = []; + } +} + +async function init() { + renderI18n(); + $("[data-action='back']").addEventListener("click", closePage); + + try { + await connectToApp(); + await fetchPolicies(); + } catch (error) { + console.error("Failed to fetch team policy:", error); + state.policies = []; + showToast(error.response?.errorMsg || error.message); + } finally { + renderPolicies(); + } +} + +init(); diff --git a/h5/policy/index.html b/h5/policy/index.html index 8b18e28..d0922ad 100644 --- a/h5/policy/index.html +++ b/h5/policy/index.html @@ -1,13 +1,19 @@ - + BD Policy - + - - +
+
+
+
+
+
+
+ diff --git a/h5/policy/policy.js b/h5/policy/policy.js new file mode 100644 index 0000000..ea84e56 --- /dev/null +++ b/h5/policy/policy.js @@ -0,0 +1,7 @@ +import { applyLang, bindHeader, connectToApp, t } from "../bd-static/shared.js"; + +const from = new URLSearchParams(window.location.search).get("from") || "BDLead"; +bindHeader(from === "BD" ? "bd_policy" : "bd_leader_policy"); +applyLang(); +document.getElementById("policyHint").textContent = from === "BD" ? t("bd_policy") : t("bd_leader_policy"); +connectToApp(); diff --git a/h5/search-payee/icons/search.svg b/h5/search-payee/icons/search.svg new file mode 100644 index 0000000..1ef5723 --- /dev/null +++ b/h5/search-payee/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/h5/search-payee/index.html b/h5/search-payee/index.html index 2ce7e86..1cbac06 100644 --- a/h5/search-payee/index.html +++ b/h5/search-payee/index.html @@ -1,13 +1,47 @@ - - + + Search Payee - + - - +
+ + +
+
+ + + +
+ +
+ +
+ + + + +
+ + diff --git a/h5/search-payee/search-payee.css b/h5/search-payee/search-payee.css new file mode 100644 index 0000000..e75d0ec --- /dev/null +++ b/h5/search-payee/search-payee.css @@ -0,0 +1,315 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.35; +} + +html, +body { + width: 100%; + min-height: 100%; + background: #edf8f1; + color: rgba(0, 0, 0, 0.8); + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; +} + +button, +input { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.search-payee { + width: 100vw; + min-height: 100vh; + background: #edf8f1; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: #f8fff9; + border-bottom: 1px solid #cce9d7; + box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14); +} + +.page-header h1 { + min-width: 0; + color: #153d28; + font-size: 1.25em; + font-weight: 800; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.header-icon, +.lang-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: #1f6f46; + font-weight: 800; +} + +.header-icon { + padding: 6px; +} + +.header-icon img { + width: 100%; + height: 100%; + display: block; +} + +.lang-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + font-size: 0.86em; +} + +.content { + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px; +} + +.search-row { + display: flex; + gap: 4px; +} + +.search-box { + flex: 1; + display: flex; + align-items: center; + gap: 4px; + border: 1px solid #cce9d7; + border-radius: 32px; + background: #fff; + padding: 4px 12px; +} + +.search-box img { + width: 1.5em; + opacity: 0.65; +} + +.search-input { + flex: 1; + width: 100%; + outline: none; + padding: 4px 0; +} + +.search-action { + color: rgba(0, 0, 0, 0.45); + font-weight: 800; +} + +.search-action.strong { + color: #173a28; +} + +.results { + margin: 6px 0; +} + +.result-card { + margin-bottom: 12px; + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #fbfffc; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); + padding: 16px; +} + +.result-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; +} + +.result-main { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 8px; +} + +.avatar-wrap { + width: 3.5em; + height: 3.5em; + flex: 0 0 3.5em; +} + +.avatar-wrap img { + display: block; + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + background: #dff5e7; +} + +.result-info { + flex: 1; + min-width: 0; +} + +.result-name { + font-weight: 900; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-id { + color: rgba(0, 0, 0, 0.45); + font-size: 0.9em; + font-weight: 700; +} + +.add-button { + width: auto; + border-radius: 12px; + background: #ff9500; + color: #fff; + font-size: 0.9em; + font-weight: 800; + padding: 4px 10px; +} + +.state { + padding: 20px 0; + color: #5f7468; + font-weight: 800; + text-align: center; +} + +.modal-layer { + position: fixed; + inset: 0; + z-index: 20; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); +} + +.modal-card { + width: 80%; + max-width: 420px; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 20px; + border-radius: 12px; + background: #fff; +} + +.modal-title { + color: rgba(0, 0, 0, 0.8); + font-size: 1.2em; + font-weight: 900; +} + +.modal-message { + color: rgba(0, 0, 0, 0.45); + font-size: 1em; + font-weight: 700; + text-align: center; +} + +.modal-actions { + width: 100%; + display: flex; + align-items: center; + justify-content: space-around; +} + +.modal-primary, +.modal-secondary { + width: 45%; + display: flex; + align-items: center; + justify-content: center; + min-height: 38px; + padding: 8px 12px; + border-radius: 70px; + font-weight: 900; +} + +.modal-primary { + background: #ff9500; + color: #fff; +} + +.modal-secondary { + border: 1px solid #ffb805; + background: #fff; + color: #ff9500; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 30; + max-width: calc(100% - 40px); + transform: translateX(-50%); + padding: 9px 13px; + border-radius: 999px; + background: rgba(22, 56, 36, 0.92); + color: #fff; + font-size: 0.9em; + font-weight: 700; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .header-icon img { + transform: scaleX(-1); +} + +@media screen and (max-width: 360px) { + * { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + * { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + * { + font-size: 24px; + } +} diff --git a/h5/search-payee/search-payee.js b/h5/search-payee/search-payee.js new file mode 100644 index 0000000..280d445 --- /dev/null +++ b/h5/search-payee/search-payee.js @@ -0,0 +1,387 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; +const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png"; +const PAYEE_KEY = "transfer.payee"; +const icons = { + back: "../host-center/icons/back.svg", + search: "./icons/search.svg" +}; + +let commonHeaders = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai" +}; + +const i18n = { + en: { + search_payee: "Search Payee", + search_user: "Search user", + search: "Search", + cancel: "Cancel", + searching: "Searching", + tips: "Tips", + confirm_transfer_to_user: "Confirm transfer to this user?", + confirm: "Confirm", + add: "Add", + user_id_prefix: "ID:", + user_not_found_or_search_failed: "User not found or search failed" + }, + ar: { + search_payee: "بحث عن المستلم", + search_user: "ابحث عن المستخدم", + search: "بحث", + cancel: "إلغاء", + searching: "جاري البحث", + tips: "نصائح", + confirm_transfer_to_user: "تأكيد التحويل إلى هذا المستخدم؟", + confirm: "تأكيد", + add: "إضافة", + user_id_prefix: "ID:", + user_not_found_or_search_failed: "لم يتم العثور على المستخدم أو فشل البحث" + }, + tr: { + search_payee: "Alıcı Ara", + search_user: "Kullanıcı ara", + search: "Ara", + cancel: "İptal", + searching: "Aranıyor", + tips: "İpuçları", + confirm_transfer_to_user: "Bu kullanıcıya transferi onaylıyor musunuz?", + confirm: "Onayla", + add: "Ekle", + user_id_prefix: "ID:", + user_not_found_or_search_failed: "Kullanıcı bulunamadı veya arama başarısız" + }, + bn: { + search_payee: "প্রাপক সার্চ", + search_user: "ব্যবহারকারী সার্চ", + search: "সার্চ", + cancel: "বাতিল", + searching: "সার্চ হচ্ছে", + tips: "টিপস", + confirm_transfer_to_user: "এই ব্যবহারকারীকে ট্রান্সফার নিশ্চিত করবেন?", + confirm: "নিশ্চিত", + add: "যোগ", + user_id_prefix: "ID:", + user_not_found_or_search_failed: "ব্যবহারকারী পাওয়া যায়নি বা সার্চ ব্যর্থ" + } +}; + +const state = { + lang: resolveLanguage(), + query: "", + results: [], + loading: false, + searched: false, + selected: null +}; + +const $ = (selector) => document.querySelector(selector); +const $$ = (selector) => Array.from(document.querySelectorAll(selector)); +const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const lang = params.get("lang") || navigator.language || "en"; + if (lang.startsWith("ar")) return "ar"; + if (lang.startsWith("tr")) return "tr"; + if (lang.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAccessOrigin(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + reqLang: data["Req-Lang"], + authorization: data.Authorization, + buildVersion: appIntel.build, + appVersion: appIntel.version, + appChannel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + sysOrigin: sysOrigin.origin, + sysOriginChild: sysOrigin.child + }; +} + +function setHeadersFromApp(headers) { + const next = {}; + if (headers.authorization) next.authorization = headers.authorization; + if (headers.reqLang) next["req-lang"] = headers.reqLang; + if (headers.appVersion || headers.buildVersion || headers.appChannel) { + const intel = []; + if (headers.appVersion) intel.push(`version=${headers.appVersion}`); + if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); + if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); + next["req-app-intel"] = intel.join(";"); + } + if (headers.reqImei) next["req-imei"] = headers.reqImei; + if (headers.sysOrigin) { + const origin = [`origin=${headers.sysOrigin}`]; + if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + next["req-sys-origin"] = origin.join(";"); + } + commonHeaders = { ...commonHeaders, ...next }; +} + +function connectToApp() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!(window.app || window.webkit || window.FlutterPageControl)) { + setHeadersFromApp({ + authorization: "", + reqLang: state.lang, + appVersion: "1.0.0", + buildVersion: "1", + appChannel: "Web", + reqImei: "H5-STATIC", + sysOrigin: "LIKEI", + sysOriginChild: "LIKEI" + }); + resolve({ success: true }); + return; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + const raw = window.app.getAccessOrigin(); + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + resolve({ success: true }); + return; + } + if (attempt < 50) { + window.setTimeout(poll, 100); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + resolve({ success: true }); + }; + poll(); + }).then((result) => { + if (typeof result === "string") setHeadersFromApp(parseAccessOrigin(result)); + return result; + }); +} + +async function apiGet(path) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: "GET", + headers: commonHeaders + }); + const data = await response.json().catch(() => ({})); + if (!response.ok || data.status === false) { + const error = new Error(data.errorMsg || data.message || `HTTP ${response.status}`); + error.response = data; + throw error; + } + return data; +} + +async function searchPayee() { + const query = $("#searchInput").value.trim(); + state.query = query; + if (!query) { + state.results = []; + renderResults(); + return; + } + state.loading = true; + state.searched = false; + renderResults(); + try { + const result = await apiGet(`/wallet/bank/search/user-profile?account=${encodeURIComponent(query)}&type=AGENT&searchType=TRANSFER`); + if (result.status && result.body) { + state.results = [{ + id: result.body.id, + account: result.body.account, + name: result.body.userNickname, + avatar: result.body.userAvatar, + sex: result.body.userSex, + age: result.body.age, + countryName: result.body.countryName, + countryCode: result.body.countryCode, + accountStatus: result.body.accountStatus, + goodID: result.body.ownSpecialId?.account + }]; + } else { + state.results = []; + } + } catch (error) { + console.error("Search failed:", error); + state.results = []; + showToast(t("user_not_found_or_search_failed")); + } finally { + state.loading = false; + state.searched = true; + renderResults(); + } +} + +function resetSearch() { + state.searched = false; + state.query = ""; + state.results = []; + $("#searchInput").value = ""; + renderResults(); +} + +function showToast(message) { + const toast = $("#toast"); + toast.textContent = message || "Request failed"; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function showConfirm(user) { + state.selected = user; + $("#confirmModal").hidden = false; +} + +function hideConfirm() { + state.selected = null; + $("#confirmModal").hidden = true; +} + +function normalizeRoute(target) { + const url = new URL(target, window.location.origin); + const currentLang = new URLSearchParams(window.location.search).get("lang"); + if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang); + return `${url.pathname}${url.search}${url.hash}`; +} + +function goBackToTransfer() { + const from = new URLSearchParams(window.location.search).get("from") || "/transfer"; + window.location.href = normalizeRoute(from); +} + +function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +function renderI18n() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + $(".lang-button").textContent = state.lang.toUpperCase(); + $$("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); + $$("[data-i18n-placeholder]").forEach((node) => { + node.placeholder = t(node.dataset.i18nPlaceholder); + }); +} + +function renderIcons() { + $("#backIcon").src = icons.back; + $("#searchIcon").src = icons.search; +} + +function renderSearchActions() { + const hasQuery = Boolean($("#searchInput").value.trim()); + $("#searchButton").hidden = state.searched || !hasQuery; + $("#cancelButton").hidden = !state.searched && !hasQuery; +} + +function renderResults() { + $("#loadingState").hidden = !state.loading; + renderSearchActions(); + const root = $("#results"); + root.innerHTML = ""; + if (state.loading) return; + state.results.forEach((user) => { + const card = document.createElement("div"); + card.className = "result-card"; + card.innerHTML = ` +
+
+
+
+
+
${t("user_id_prefix")}
+
+
+ +
`; + card.querySelector(".result-name").textContent = user.name; + card.querySelector(".result-id span:last-child").textContent = user.goodID || user.account || "-"; + card.querySelector(".add-button").addEventListener("click", () => showConfirm(user)); + card.querySelector("img").addEventListener("error", (event) => { + event.target.onerror = null; + event.target.src = DEFAULT_AVATAR; + }); + root.appendChild(card); + }); +} + +function render() { + renderI18n(); + renderIcons(); + renderResults(); +} + +function bindEvents() { + $("[data-action='back']").addEventListener("click", closePage); + $(".lang-button").addEventListener("click", () => { + window.location.href = normalizeRoute("/language"); + }); + $("#searchInput").addEventListener("input", () => { + if (state.searched) state.searched = false; + renderSearchActions(); + }); + $("#searchInput").addEventListener("keyup", (event) => { + if (event.key === "Enter") searchPayee(); + }); + $("#searchButton").addEventListener("click", searchPayee); + $("#cancelButton").addEventListener("click", resetSearch); + $("#confirmModal").addEventListener("click", hideConfirm); + $(".modal-card").addEventListener("click", (event) => event.stopPropagation()); + $("[data-action='cancel-select']").addEventListener("click", hideConfirm); + $("[data-action='confirm-select']").addEventListener("click", () => { + if (!state.selected) return; + sessionStorage.setItem(PAYEE_KEY, JSON.stringify(state.selected)); + hideConfirm(); + goBackToTransfer(); + }); +} + +async function init() { + bindEvents(); + render(); + await connectToApp(); +} + +init(); diff --git a/h5/transfer/icons/dollar.svg b/h5/transfer/icons/dollar.svg new file mode 100644 index 0000000..e5da666 --- /dev/null +++ b/h5/transfer/icons/dollar.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/h5/transfer/index.html b/h5/transfer/index.html index 7d6a254..bb25f11 100644 --- a/h5/transfer/index.html +++ b/h5/transfer/index.html @@ -1,13 +1,67 @@ - - + + Transfer - + - - +
+ + +
+
+
+
Information
+ +
+
+ Available salaries + + 0.00 +
+
+ +
+
Transfer to others
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ + +
+ + diff --git a/h5/transfer/transfer.css b/h5/transfer/transfer.css new file mode 100644 index 0000000..d6d2ff0 --- /dev/null +++ b/h5/transfer/transfer.css @@ -0,0 +1,332 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.35; +} + +html, +body { + width: 100%; + min-height: 100%; + background: #edf8f1; + color: rgba(0, 0, 0, 0.8); + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.transfer { + width: 100vw; + min-height: 100vh; + background: #edf8f1; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: #f8fff9; + border-bottom: 1px solid #cce9d7; + box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14); +} + +.page-header h1 { + min-width: 0; + color: #153d28; + font-size: 1.25em; + font-weight: 800; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.header-icon, +.lang-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: #1f6f46; + font-weight: 800; +} + +.header-icon { + padding: 6px; +} + +.header-icon img { + width: 100%; + height: 100%; + display: block; +} + +.lang-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + font-size: 0.86em; +} + +.content { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px; +} + +.section-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.section-title { + margin-bottom: 10px; + color: #244735; + font-weight: 800; +} + +.section-row .section-title { + margin-bottom: 0; +} + +.details-link { + display: flex; + align-items: center; + color: rgba(0, 0, 0, 0.42); + font-weight: 700; +} + +.details-link img { + width: 16px; + height: 16px; + margin-left: 4px; +} + +.surface-card { + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #fbfffc; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.info-card { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; +} + +.info-label { + font-size: 1em; + font-weight: 700; +} + +.balance-value { + color: #173a28; + font-size: 1.1em; + font-weight: 900; +} + +.loading-text { + color: #6d7e72; + font-weight: 700; +} + +.payee-card { + min-height: 78px; +} + +.payee-empty { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.search-button { + padding: 10px 16px; + border-radius: 8px; + background: #fff3c8; + color: rgba(0, 0, 0, 0.8); + font-weight: 800; +} + +.payee-selected { + width: 100%; +} + +.payee-row { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; +} + +.avatar-wrap { + width: 3.5em; + height: 3.5em; + flex: 0 0 3.5em; +} + +.avatar-wrap img { + display: block; + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + background: #dff5e7; +} + +.payee-info { + flex: 1; + min-width: 0; +} + +.payee-name { + margin-bottom: 4px; + color: #333; + font-weight: 800; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.payee-id { + color: #666; + font-size: 0.9em; +} + +.change-button { + padding: 2px 8px; + border: 1px solid #d9eadf; + border-radius: 12px; + color: rgba(0, 0, 0, 0.42); + font-weight: 700; +} + +.amount-card { + margin-bottom: 12px; + padding: 16px; +} + +.amount-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.coin-item { + aspect-ratio: 1 / 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 2px solid #cce9d7; + border-radius: 12px; + background: #e9f8ef; + cursor: pointer; + transition: transform 0.2s, border-color 0.2s, background 0.2s; +} + +.coin-item.selected { + border-color: #49a86d; + background: #c9efd7; +} + +.coin-item:active { + transform: scale(0.98); +} + +.coin-item img { + width: 40%; + margin-bottom: 6px; +} + +.coin-price { + color: #131111; + font-weight: 800; +} + +.button-wrap { + display: flex; + justify-content: center; + margin-top: 10px; +} + +.transfer-button { + width: 70%; + border-radius: 32px; + background: #ff9500; + padding: 12px; + color: #fff; + font-weight: 800; +} + +.transfer-button:disabled { + background: #c5c5c5; + color: #fff; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 30; + max-width: calc(100% - 40px); + transform: translateX(-50%); + padding: 9px 13px; + border-radius: 999px; + background: rgba(22, 56, 36, 0.92); + color: #fff; + font-size: 0.9em; + font-weight: 700; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .details-link img, +[dir="rtl"] .header-icon img { + transform: scaleX(-1); +} + +@media screen and (max-width: 360px) { + * { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + * { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + * { + font-size: 24px; + } +} diff --git a/h5/transfer/transfer.js b/h5/transfer/transfer.js new file mode 100644 index 0000000..d8d9e00 --- /dev/null +++ b/h5/transfer/transfer.js @@ -0,0 +1,438 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; +const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png"; +const PAYEE_KEY = "transfer.payee"; +const icons = { + back: "../host-center/icons/back.svg", + arrow: "../host-center/icons/arrow.svg", + dollar: "./icons/dollar.svg" +}; + +let commonHeaders = { + authorization: "Bearer undefined", + "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", + "req-client": "Android", + "req-imei": "8fa54d728ab449e04f9292329ed44bda", + "req-lang": "id-ID", + "req-sys-origin": "origin=ATYOU;originChild=ATYOU", + "req-version": "V2", + "req-zone": "Asia/Shanghai" +}; + +let preservePayeeOnLeave = false; + +const i18n = { + en: { + transfer: "Transfer", + information: "Information", + details: "Details", + available_salaries: "Available salaries", + loading: "Loading", + transfer_to_others: "Transfer to others", + search: "Search", + change: "Change", + user_id_prefix: "ID:", + failed_to_load_balance: "Failed to load balance", + please_select_amount: "Please select amount", + please_select_payee: "Please select payee", + transfer_dollar_success: "Transfer ${price} to {name} successfully" + }, + ar: { + transfer: "تحويل", + information: "المعلومات", + details: "التفاصيل", + available_salaries: "الرواتب المتاحة", + loading: "تحميل", + transfer_to_others: "تحويل للآخرين", + search: "بحث", + change: "تغيير", + user_id_prefix: "ID:", + failed_to_load_balance: "فشل تحميل الرصيد", + please_select_amount: "يرجى اختيار المبلغ", + please_select_payee: "يرجى اختيار المستلم", + transfer_dollar_success: "تم تحويل ${price} إلى {name} بنجاح" + }, + tr: { + transfer: "Transfer", + information: "Bilgi", + details: "Detaylar", + available_salaries: "Kullanılabilir maaşlar", + loading: "Yükleniyor", + transfer_to_others: "Başkalarına transfer", + search: "Ara", + change: "Değiştir", + user_id_prefix: "ID:", + failed_to_load_balance: "Bakiye yüklenemedi", + please_select_amount: "Lütfen tutar seçin", + please_select_payee: "Lütfen alıcı seçin", + transfer_dollar_success: "${price}, {name} kullanıcısına başarıyla aktarıldı" + }, + bn: { + transfer: "ট্রান্সফার", + information: "তথ্য", + details: "বিস্তারিত", + available_salaries: "উপলভ্য বেতন", + loading: "লোড হচ্ছে", + transfer_to_others: "অন্যদের ট্রান্সফার", + search: "সার্চ", + change: "পরিবর্তন", + user_id_prefix: "ID:", + failed_to_load_balance: "ব্যালেন্স লোড করতে ব্যর্থ", + please_select_amount: "পরিমাণ নির্বাচন করুন", + please_select_payee: "প্রাপক নির্বাচন করুন", + transfer_dollar_success: "${price} {name} কে সফলভাবে ট্রান্সফার হয়েছে" + } +}; + +const state = { + lang: resolveLanguage(), + balance: "0.00", + loading: false, + selectedId: null, + payee: { + id: null, + account: "", + name: "", + avatar: "", + type: "", + isHost: false, + isAgency: false, + hasSalary: false + }, + amounts: [ + { 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 $ = (selector) => document.querySelector(selector); +const $$ = (selector) => Array.from(document.querySelectorAll(selector)); +const t = (key, params = {}) => { + let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; + Object.entries(params).forEach(([name, value]) => { + text = text.replaceAll(`{${name}}`, value); + }); + return text; +}; + +function resolveLanguage() { + const params = new URLSearchParams(window.location.search); + const lang = params.get("lang") || navigator.language || "en"; + if (lang.startsWith("ar")) return "ar"; + if (lang.startsWith("tr")) return "tr"; + if (lang.startsWith("bn")) return "bn"; + return "en"; +} + +function splitHeaderPairs(value) { + const result = {}; + if (!value) return result; + value.split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function parseAccessOrigin(raw) { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + return { + reqLang: data["Req-Lang"], + authorization: data.Authorization, + buildVersion: appIntel.build, + appVersion: appIntel.version, + appChannel: appIntel.channel, + reqImei: appIntel["Req-Imei"], + sysOrigin: sysOrigin.origin, + sysOriginChild: sysOrigin.child + }; +} + +function setHeadersFromApp(headers) { + const next = {}; + if (headers.authorization) next.authorization = headers.authorization; + if (headers.reqLang) next["req-lang"] = headers.reqLang; + if (headers.appVersion || headers.buildVersion || headers.appChannel) { + const intel = []; + if (headers.appVersion) intel.push(`version=${headers.appVersion}`); + if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); + if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); + next["req-app-intel"] = intel.join(";"); + } + if (headers.reqImei) next["req-imei"] = headers.reqImei; + if (headers.sysOrigin) { + const origin = [`origin=${headers.sysOrigin}`]; + if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + next["req-sys-origin"] = origin.join(";"); + } + commonHeaders = { ...commonHeaders, ...next }; +} + +function connectToApp() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!(window.app || window.webkit || window.FlutterPageControl)) { + setHeadersFromApp({ + authorization: "", + reqLang: state.lang, + appVersion: "1.0.0", + buildVersion: "1", + appChannel: "Web", + reqImei: "H5-STATIC", + sysOrigin: "LIKEI", + sysOriginChild: "LIKEI" + }); + resolve({ success: true }); + return; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + const raw = window.app.getAccessOrigin(); + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + resolve({ success: true }); + return; + } + if (attempt < 50) { + window.setTimeout(poll, 100); + return; + } + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { + window.FlutterApp.postMessage("requestAccessOrigin"); + } + resolve({ success: true }); + }; + poll(); + }).then((result) => { + if (typeof result === "string") setHeadersFromApp(parseAccessOrigin(result)); + return result; + }); +} + +async function request(method, path, body) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method, + headers: { + ...commonHeaders, + "content-type": "application/json" + }, + body: body === undefined ? undefined : JSON.stringify(body) + }); + const data = await response.json().catch(() => ({})); + if (!response.ok || data.status === false) { + const error = new Error(data.errorMsg || data.message || `HTTP ${response.status}`); + error.response = data; + throw error; + } + return data; +} + +const apiGet = (path) => request("GET", path); +const apiPost = (path, body) => request("POST", path, body); + +function formatMoney(value) { + const number = Number(value || 0); + return Number.isFinite(number) ? number.toFixed(2) : "0.00"; +} + +function getStoredPayee() { + try { + const payee = JSON.parse(sessionStorage.getItem(PAYEE_KEY) || "null"); + if (payee) Object.assign(state.payee, payee); + } catch (error) { + console.error("Failed to parse transfer payee:", error); + } +} + +function clearPayee() { + Object.assign(state.payee, { + id: null, + account: "", + name: "", + avatar: "", + type: "", + isHost: false, + isAgency: false, + hasSalary: false + }); + sessionStorage.removeItem(PAYEE_KEY); +} + +async function fetchBalance() { + state.loading = true; + renderBalance(); + try { + const result = await apiGet("/wallet/bank/balance"); + state.balance = result.status && typeof result.body === "number" ? result.body.toFixed(2) : "0.00"; + } catch (error) { + console.error("Failed to fetch bank balance:", error); + state.balance = "0.00"; + showToast(t("failed_to_load_balance")); + } finally { + state.loading = false; + renderBalance(); + } +} + +async function submitTransfer() { + if (!state.selectedId) { + showToast(t("please_select_amount")); + return; + } + if (!state.payee.id) { + showToast(t("please_select_payee")); + return; + } + const selected = state.amounts.find((item) => item.id === state.selectedId); + const payload = { + amount: selected.price, + acceptUserId: state.payee.id, + acceptMethod: "BANK_TRANSFER" + }; + try { + const result = await apiPost("/wallet/bank/transferGold", payload); + if (result.status) { + showToast(t("transfer_dollar_success", { price: selected.price, name: state.payee.name })); + state.selectedId = null; + await fetchBalance(); + renderAmounts(); + } + } catch (error) { + console.error("Transfer failed:", error); + showToast(error.response?.errorMsg || error.message); + } +} + +function normalizeRoute(target) { + const url = new URL(target, window.location.origin); + const currentLang = new URLSearchParams(window.location.search).get("lang"); + if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang); + return `${url.pathname}${url.search}${url.hash}`; +} + +function navigateTo(target) { + window.location.href = normalizeRoute(target); +} + +function closePage() { + if (window.history.length > 1) { + window.history.back(); + return; + } + if (window.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + } +} + +function showToast(message) { + const toast = $("#toast"); + toast.textContent = message || "Request failed"; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function renderI18n() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + $(".lang-button").textContent = state.lang.toUpperCase(); + $$("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); +} + +function renderIcons() { + $("#backIcon").src = icons.back; + $("#detailsArrow").src = icons.arrow; +} + +function renderBalance() { + $("#balanceLoading").hidden = !state.loading; + $("#balanceValue").hidden = state.loading; + $("#balanceValue").textContent = state.balance; +} + +function renderPayee() { + const hasPayee = Boolean(state.payee.id); + $("#payeeSelected").hidden = !hasPayee; + $("#payeeEmpty").hidden = hasPayee; + if (!hasPayee) return; + $("#payeeName").textContent = state.payee.name; + $("#payeeAccount").textContent = state.payee.goodID || state.payee.account || "-"; + const avatar = $("#payeeAvatar"); + avatar.src = state.payee.avatar || ""; + avatar.alt = state.payee.name || ""; +} + +function renderAmounts() { + const root = $("#amountGrid"); + root.innerHTML = ""; + state.amounts.forEach((item) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = `coin-item${state.selectedId === item.id ? " selected" : ""}`; + button.innerHTML = `
$${item.price}
`; + button.addEventListener("click", () => { + state.selectedId = state.selectedId === item.id ? null : item.id; + renderAmounts(); + }); + root.appendChild(button); + }); + $("#transferButton").disabled = !state.selectedId || !state.payee.id; +} + +function render() { + renderI18n(); + renderIcons(); + renderBalance(); + renderPayee(); + renderAmounts(); +} + +function bindEvents() { + $("[data-action='back']").addEventListener("click", closePage); + $(".lang-button").addEventListener("click", () => navigateTo("/language")); + $$("[data-route]").forEach((node) => { + node.addEventListener("click", () => navigateTo(node.dataset.route)); + }); + $$("[data-action='search-payee']").forEach((node) => { + node.addEventListener("click", () => { + clearPayee(); + preservePayeeOnLeave = true; + navigateTo("/search-payee?from=/transfer"); + }); + }); + $("#transferButton").addEventListener("click", submitTransfer); + window.addEventListener("pagehide", () => { + if (!preservePayeeOnLeave) sessionStorage.removeItem(PAYEE_KEY); + }); + $("#payeeAvatar").addEventListener("error", (event) => { + event.target.onerror = null; + event.target.src = DEFAULT_AVATAR; + }); +} + +async function init() { + getStoredPayee(); + bindEvents(); + render(); + await connectToApp(); + await fetchBalance(); +} + +init(); diff --git a/h5/yumi-invite-agency/yumi-invite-agency.js b/h5/yumi-invite-agency/yumi-invite-agency.js index 49c6dad..9582881 100644 --- a/h5/yumi-invite-agency/yumi-invite-agency.js +++ b/h5/yumi-invite-agency/yumi-invite-agency.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const TEXT = { pageTitle: "Invite User To Become Agent", diff --git a/h5/yumi-invite-bd-leader/yumi-invite-bd-leader.js b/h5/yumi-invite-bd-leader/yumi-invite-bd-leader.js index 8437fba..be83bfe 100644 --- a/h5/yumi-invite-bd-leader/yumi-invite-bd-leader.js +++ b/h5/yumi-invite-bd-leader/yumi-invite-bd-leader.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-bdleader-message-own", invite: "/team/bd/invite-bdleader", diff --git a/h5/yumi-invite-bd/yumi-invite-bd.js b/h5/yumi-invite-bd/yumi-invite-bd.js index 763787b..9bef61b 100644 --- a/h5/yumi-invite-bd/yumi-invite-bd.js +++ b/h5/yumi-invite-bd/yumi-invite-bd.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-bd-message-own", invite: "/team/bd/invite-bd", diff --git a/h5/yumi-invite-recharge-agency/yumi-invite-recharge-agency.js b/h5/yumi-invite-recharge-agency/yumi-invite-recharge-agency.js index 64453ca..2c210bd 100644 --- a/h5/yumi-invite-recharge-agency/yumi-invite-recharge-agency.js +++ b/h5/yumi-invite-recharge-agency/yumi-invite-recharge-agency.js @@ -2,7 +2,7 @@ const API_BASE = "https://jvapi.haiyihy.com"; const BACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg=="; - const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png"; + const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k.png"; const ENDPOINTS = { invitedList: "/team/bd/invite-recharge-agent-message-own", invite: "/team/bd/invite-recharge-agent",