388 lines
12 KiB
JavaScript
388 lines
12 KiB
JavaScript
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 = `
|
||
<div class="result-row">
|
||
<div class="result-main">
|
||
<div class="avatar-wrap"><img src="${user.avatar || ""}" alt="" /></div>
|
||
<div class="result-info">
|
||
<div class="result-name"></div>
|
||
<div class="result-id"><span>${t("user_id_prefix")}</span> <span></span></div>
|
||
</div>
|
||
</div>
|
||
<button class="add-button" type="button">${t("add")}</button>
|
||
</div>`;
|
||
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();
|