298 lines
10 KiB
JavaScript
298 lines
10 KiB
JavaScript
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 fallbackConfig = {
|
||
title: "Center",
|
||
identity: "USER",
|
||
heroTitle: "Available salary",
|
||
heroLink: "Details",
|
||
heroRoute: "/history-salary",
|
||
balanceType: "",
|
||
summaries: [],
|
||
sections: []
|
||
};
|
||
|
||
const config = { ...fallbackConfig, ...(window.CENTER_PAGE || {}) };
|
||
const state = {
|
||
lang: resolveLanguage(),
|
||
heroValue: "0",
|
||
summaries: [...(config.summaries || [])]
|
||
};
|
||
|
||
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 formatMoney(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);
|
||
}
|
||
|
||
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 requestAccessOrigin() {
|
||
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");
|
||
}
|
||
};
|
||
poll();
|
||
});
|
||
}
|
||
|
||
async function connectToApp() {
|
||
const raw = await requestAccessOrigin();
|
||
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;
|
||
}
|
||
|
||
async function fetchHeroValue() {
|
||
if (config.balanceType) {
|
||
const result = await apiGet(`/wallet/salary-account/balance?salaryType=${config.balanceType}`);
|
||
if (result.status && result.body) state.heroValue = formatMoney(result.body.availableBalance || 0);
|
||
return;
|
||
}
|
||
if (config.balanceEndpoint) {
|
||
const result = await apiGet(config.balanceEndpoint);
|
||
const body = result.body || {};
|
||
state.heroValue = formatMoney(body.currentSalary ?? body.availableBalance ?? body.balance ?? 0);
|
||
}
|
||
}
|
||
|
||
function showToast(message) {
|
||
const toast = document.getElementById("toast");
|
||
toast.textContent = message;
|
||
toast.hidden = false;
|
||
window.clearTimeout(showToast.timer);
|
||
showToast.timer = window.setTimeout(() => {
|
||
toast.hidden = true;
|
||
}, 2500);
|
||
}
|
||
|
||
function normalizeRoute(target) {
|
||
const alias = {
|
||
"/invite-agency": "/invite-agency/",
|
||
"/invite-bd": "/invite-bd/",
|
||
"/invite-bd-leader": "/invite-bd-leader/",
|
||
"/invite-recharge-agency": "/invite-recharge-agency/"
|
||
};
|
||
const url = new URL(alias[target] || 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 menuIcon(text) {
|
||
return (text || "?").trim().slice(0, 1).toUpperCase();
|
||
}
|
||
|
||
function render() {
|
||
document.documentElement.lang = state.lang;
|
||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||
document.getElementById("pageTitle").textContent = config.title;
|
||
document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase();
|
||
document.getElementById("heroTitle").textContent = config.heroTitle;
|
||
document.getElementById("heroLink").textContent = config.heroLink || "Details";
|
||
document.getElementById("heroValue").textContent = `$${state.heroValue}`;
|
||
|
||
const summaryRoot = document.getElementById("summaryGrid");
|
||
summaryRoot.innerHTML = "";
|
||
state.summaries.forEach((item) => {
|
||
const node = document.createElement("div");
|
||
node.className = "summary-item";
|
||
node.innerHTML = `<div class="summary-label"></div><div class="summary-value"></div>`;
|
||
node.querySelector(".summary-label").textContent = item.label;
|
||
node.querySelector(".summary-value").textContent = item.value;
|
||
summaryRoot.appendChild(node);
|
||
});
|
||
summaryRoot.hidden = !state.summaries.length;
|
||
|
||
const oldTask = document.getElementById("taskCard");
|
||
if (oldTask) oldTask.remove();
|
||
if (config.task) {
|
||
const taskCard = document.createElement("section");
|
||
taskCard.className = "surface-card task-card";
|
||
taskCard.id = "taskCard";
|
||
taskCard.innerHTML = `<div class="task-title"></div><div class="task-grid"></div>`;
|
||
taskCard.querySelector(".task-title").textContent = config.task.title || "";
|
||
const taskGrid = taskCard.querySelector(".task-grid");
|
||
(config.task.items || []).forEach((item) => {
|
||
const node = document.createElement("div");
|
||
node.innerHTML = `<div class="task-label"></div><div class="task-value"></div>`;
|
||
node.querySelector(".task-label").textContent = item.label;
|
||
node.querySelector(".task-value").textContent = item.value;
|
||
taskGrid.appendChild(node);
|
||
});
|
||
const sections = document.getElementById("sections");
|
||
sections.parentNode.insertBefore(taskCard, sections);
|
||
}
|
||
|
||
const sectionRoot = document.getElementById("sections");
|
||
sectionRoot.innerHTML = "";
|
||
(config.sections || []).forEach((section) => {
|
||
const card = document.createElement("section");
|
||
card.className = "surface-card menu-card";
|
||
const title = document.createElement("div");
|
||
title.className = "section-title";
|
||
title.textContent = section.title || "";
|
||
card.appendChild(title);
|
||
|
||
(section.items || []).forEach((item) => {
|
||
const row = document.createElement("button");
|
||
row.type = "button";
|
||
row.className = "menu-row";
|
||
row.dataset.route = item.route;
|
||
row.innerHTML = `<div class="menu-label"><span class="menu-icon"></span><span class="menu-text"></span></div><span class="arrow">›</span>`;
|
||
row.querySelector(".menu-icon").textContent = item.icon || menuIcon(item.label);
|
||
row.querySelector(".menu-text").textContent = item.label;
|
||
row.addEventListener("click", () => navigateTo(item.route));
|
||
card.appendChild(row);
|
||
});
|
||
sectionRoot.appendChild(card);
|
||
});
|
||
}
|
||
|
||
function bindEvents() {
|
||
document.querySelector("[data-action='back']").addEventListener("click", () => {
|
||
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");
|
||
}
|
||
});
|
||
document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
|
||
document.getElementById("heroLink").addEventListener("click", () => navigateTo(config.heroRoute || "/history-salary"));
|
||
}
|
||
|
||
async function init() {
|
||
localStorage.setItem("identity", JSON.stringify({ identity: config.identity }));
|
||
render();
|
||
bindEvents();
|
||
try {
|
||
await connectToApp();
|
||
await fetchHeroValue();
|
||
} catch (error) {
|
||
console.warn(`${config.title} data request failed:`, error);
|
||
showToast(error.response?.errorMsg || error.message || "Failed to load data.");
|
||
} finally {
|
||
render();
|
||
}
|
||
}
|
||
|
||
init();
|