export const API_BASE_URL = "https://jvapi.haiyihy.com"; const text = { en: { language: "Language", 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: "", "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(); let appConnectionPromise = null; 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") || localStorage.getItem("preferred-language") || 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 mapHeaderValue(target, key, value) { if (value === null || value === undefined || value === "" || target[key]) return; target[key] = String(value); } function normalizeHeaderCandidate(value, depth = 0) { if (depth > 4 || value === null || value === undefined) return {}; if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed) return {}; if (trimmed[0] === "{" || trimmed[0] === "[") { try { return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1); } catch { return {}; } } return /^bearer\s+/i.test(trimmed) ? { authorization: trimmed } : {}; } if (Array.isArray(value)) { return value.reduce((result, item) => ({ ...result, ...normalizeHeaderCandidate(item, depth + 1) }), {}); } if (typeof value !== "object") return {}; const appIntel = splitHeaderPairs(value["Req-App-Intel"] || value["req-app-intel"]); const sysOrigin = splitHeaderPairs(value["Req-Sys-Origin"] || value["req-sys-origin"]); const normalized = {}; mapHeaderValue(normalized, "authorization", value.Authorization || value.authorization || value.token || value.accessToken); mapHeaderValue(normalized, "reqLang", value["Req-Lang"] || value["req-lang"] || value.reqLang || value.lang || value.language); mapHeaderValue(normalized, "appVersion", appIntel.version || value["App-Version"] || value.appVersion); mapHeaderValue(normalized, "buildVersion", appIntel.build || value["Build-Version"] || value.buildVersion); mapHeaderValue(normalized, "channel", appIntel.channel || value["App-Channel"] || value.appChannel || value.channel); mapHeaderValue(normalized, "reqImei", value["Req-Imei"] || value["req-imei"] || value.reqImei || appIntel["Req-Imei"] || appIntel.reqImei); mapHeaderValue(normalized, "origin", sysOrigin.origin || value["Sys-Origin"] || value.sysOrigin || value.origin); mapHeaderValue(normalized, "child", sysOrigin.originChild || sysOrigin.child || value["Sys-Origin-Child"] || value.sysOriginChild || value.child); ["headerInfo", "headers", "auth", "data", "state"].forEach((key) => { Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1)); }); Object.keys(value).forEach((key) => { if (["headerInfo", "headers", "auth", "data", "state"].includes(key)) return; if (value[key] && typeof value[key] === "object") { Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1)); } }); return normalized; } function collectHeaderCandidate(collected, value) { const normalized = normalizeHeaderCandidate(value); if (Object.keys(normalized).length) collected.push(normalized); } function collectHeadersFromStorage(collected, storage) { if (!storage) return; ["authorization", "Authorization", "token", "accessToken", "headerInfo", "headersFromApp", "appHeaders", "user", "userInfo"].forEach((key) => { try { collectHeaderCandidate(collected, storage.getItem(key)); } catch {} }); try { for (let index = 0; index < storage.length; index += 1) { collectHeaderCandidate(collected, storage.getItem(storage.key(index))); } } catch {} } function queryHeaderCandidate() { const params = new URLSearchParams(window.location.search); return { authorization: params.get("authorization") || params.get("Authorization") || params.get("token"), reqLang: params.get("lang"), appVersion: params.get("appVersion"), buildVersion: params.get("buildVersion"), channel: params.get("appChannel") || params.get("channel"), reqImei: params.get("reqImei"), origin: params.get("sysOrigin"), child: params.get("sysOriginChild") }; } function discoverAppHeaders() { const collected = [{ reqLang: lang }, queryHeaderCandidate()]; collectHeaderCandidate(collected, window.__APP_HEADERS__); collectHeaderCandidate(collected, window.__HEADER_INFO__); collectHeaderCandidate(collected, window.headerInfo); collectHeadersFromStorage(collected, window.localStorage); collectHeadersFromStorage(collected, window.sessionStorage); return collected.reduce((result, item) => ({ ...result, ...item }), {}); } function applyDiscoveredHeaders() { const discovered = discoverAppHeaders(); if (headers.authorization) delete discovered.authorization; if (Object.keys(discovered).length) setHeadersFromApp(discovered); return discovered; } export function setHeadersFromApp(rawOrObject) { const data = normalizeHeaderCandidate(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 }; } function isAppEnvironment() { return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp); } function requestAccessOrigin() { return new Promise((resolve) => { let settled = false; let timeoutId = null; const finish = (raw) => { if (settled) { if (raw) setHeadersFromApp(raw); return; } settled = true; if (timeoutId) window.clearTimeout(timeoutId); resolve(raw || null); }; window.renderData = finish; window.getIosAccessOriginParam = finish; if (!isAppEnvironment()) { setHeadersFromApp({ reqLang: lang, appVersion: "1.0.0", buildVersion: "1", channel: "Web", reqImei: "H5-STATIC", origin: "LIKEI", child: "LIKEI" }); finish(null); return; } const requestFromFlutter = () => { if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { window.FlutterApp.postMessage("requestAccessOrigin"); } }; if (window.app && typeof window.app.getAccessOrigin === "function") { finish(window.app.getAccessOrigin()); return; } requestFromFlutter(); timeoutId = window.setTimeout(() => finish(null), 12000); let attempt = 0; const poll = () => { attempt += 1; if (window.app && typeof window.app.getAccessOrigin === "function") { finish(window.app.getAccessOrigin()); return; } if (attempt < 50) { window.setTimeout(poll, 100); return; } requestFromFlutter(); }; poll(); }); } export async function connectToApp() { if (!appConnectionPromise) { applyDiscoveredHeaders(); appConnectionPromise = requestAccessOrigin() .then((raw) => { if (raw) setHeadersFromApp(raw); else applyDiscoveredHeaders(); return { success: true, environment: isAppEnvironment() ? "app" : "browser", authenticated: Boolean(headers.authorization) }; }) .finally(() => { appConnectionPromise = null; }); } return appConnectionPromise; } export async function request(method, path, body) { applyDiscoveredHeaders(); const options = { method, headers: { ...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() { const params = new URLSearchParams(window.location.search); const from = params.get("from"); if (from) { const target = new URL(from, window.location.origin); const activeAction = params.get("activeAction"); if (activeAction) target.searchParams.set("activeAction", activeAction); const currentLang = params.get("lang"); if (currentLang && !target.searchParams.has("lang")) target.searchParams.set("lang", currentLang); window.location.href = `${target.pathname}${target.search}${target.hash}`; return; } 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); } const digitMaps = [ ["٠", "0"], ["١", "1"], ["٢", "2"], ["٣", "3"], ["٤", "4"], ["٥", "5"], ["٦", "6"], ["٧", "7"], ["٨", "8"], ["٩", "9"], ["۰", "0"], ["۱", "1"], ["۲", "2"], ["۳", "3"], ["۴", "4"], ["۵", "5"], ["۶", "6"], ["۷", "7"], ["۸", "8"], ["۹", "9"], ["০", "0"], ["১", "1"], ["২", "2"], ["৩", "3"], ["৪", "4"], ["৫", "5"], ["৬", "6"], ["৭", "7"], ["৮", "8"], ["৯", "9"] ]; const monthNames = new Map(Object.entries({ january: 1, jan: 1, "يناير": 1, "كانون الثاني": 1, ocak: 1, "জানুয়ারি": 1, february: 2, feb: 2, "فبراير": 2, "شباط": 2, subat: 2, "şubat": 2, "ফেব্রুয়ারি": 2, march: 3, mar: 3, "مارس": 3, "آذار": 3, mart: 3, "মার্চ": 3, april: 4, apr: 4, "أبريل": 4, "ابريل": 4, "نيسان": 4, nisan: 4, "এপ্রিল": 4, may: 5, "مايو": 5, "أيار": 5, mayis: 5, "mayıs": 5, "মে": 5, june: 6, jun: 6, "يونيو": 6, "حزيران": 6, haziran: 6, "জুন": 6, july: 7, jul: 7, "يوليو": 7, "تموز": 7, temmuz: 7, "জুলাই": 7, august: 8, aug: 8, "أغسطس": 8, "اغسطس": 8, "آب": 8, agustos: 8, "ağustos": 8, "আগস্ট": 8, september: 9, sep: 9, "سبتمبر": 9, "أيلول": 9, eylul: 9, "eylül": 9, "সেপ্টেম্বর": 9, october: 10, oct: 10, "أكتوبر": 10, "اكتوبر": 10, "تشرين الأول": 10, ekim: 10, "অক্টোবর": 10, november: 11, nov: 11, "نوفمبر": 11, "تشرين الثاني": 11, kasim: 11, "kasım": 11, "নভেম্বর": 11, december: 12, dec: 12, "ديسمبر": 12, "كانون الأول": 12, aralik: 12, "aralık": 12, "ডিসেম্বর": 12 })); function pad2(value) { return String(value).padStart(2, "0"); } function formatBillBelong(year, month, day, time) { const monthNumber = Number(month); const dayNumber = Number(day); if (monthNumber < 1 || monthNumber > 12 || dayNumber < 1 || dayNumber > 31) return ""; return `${year}-${pad2(monthNumber)}-${pad2(dayNumber)}T${time}`; } function normalizeDateText(value) { return digitMaps.reduce((textValue, [from, to]) => textValue.split(from).join(to), String(value || "")) .replace(/&(?:rlm|lrm|nbsp);/gi, " ") .replace(/[\u061c\u200e\u200f]/g, "") .replace(/[،]/g, ",") .replace(/\s+/g, " ") .trim(); } function normalizeTime(value, markerText = "") { const match = String(value || "").match(/(\d{1,2}):(\d{2})(?::(\d{2}))?/); if (!match) return "00:00:00"; let hour = Number(match[1]); const marker = markerText.toLowerCase(); const isPm = /\bpm\b|مساء|(?:^|\s)م(?:\s|$)/.test(marker); const isAm = /\bam\b|صباح|(?:^|\s)ص(?:\s|$)/.test(marker); if (isPm && hour < 12) hour += 12; if (isAm && hour === 12) hour = 0; return `${pad2(hour)}:${match[2]}:${match[3] || "00"}`; } export function normalizeBillBelong(value) { const textValue = normalizeDateText(value); if (!textValue) return ""; const isoMatch = textValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/); if (isoMatch) { return formatBillBelong(isoMatch[1], isoMatch[2], isoMatch[3], `${isoMatch[4] || "00"}:${isoMatch[5] || "00"}:${isoMatch[6] || "00"}`); } const compactDateTime = textValue.match(/^(\d{4})(\d{2})(\d{2})(?:\D?(\d{2})(\d{2})(\d{2})?)?$/); if (compactDateTime) { return formatBillBelong(compactDateTime[1], compactDateTime[2], compactDateTime[3], `${compactDateTime[4] || "00"}:${compactDateTime[5] || "00"}:${compactDateTime[6] || "00"}`); } const numericDate = textValue.match(/\b(?:(\d{4})[./-](\d{1,2})[./-](\d{1,2})|(\d{1,2})[./-](\d{1,2})[./-](\d{4}))\b/); if (numericDate) { const time = normalizeTime(textValue, textValue); if (numericDate[1]) return formatBillBelong(numericDate[1], numericDate[2], numericDate[3], time); const first = Number(numericDate[4]); const second = Number(numericDate[5]); const month = first > 12 && second <= 12 ? second : first; const day = second > 12 && first <= 12 ? second : first > 12 ? first : second; return formatBillBelong(numericDate[6], month, day, time); } const lowerText = textValue.toLowerCase(); let month = 0; for (const [name, valueForMonth] of monthNames) { if (lowerText.includes(name)) { month = valueForMonth; break; } } const yearMatch = textValue.match(/\b(20\d{2})\b/); if (month && yearMatch) { const withoutYear = textValue.replace(yearMatch[1], ""); const dayMatch = withoutYear.match(/\b([0-3]?\d)\b/); if (dayMatch) { return formatBillBelong(yearMatch[1], month, dayMatch[1], normalizeTime(textValue, textValue)); } } return ""; } export function historyMoreUrl(basePath, billBelong) { const normalized = normalizeBillBelong(billBelong); return normalized ? `${basePath}?billBelong=${encodeURIComponent(normalized)}` : ""; } 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) { const normalized = statusText(status); if (normalized === t("completed")) return "completed"; if (normalized === t("pending")) return "pending"; if (normalized === t("out_of_account")) 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"), "Out Of Account": t("out_of_account") }; return map[status] || status || ""; } export function isCompletedStatus(status) { return status === "SETTLED" || status === "PAY_OUT" || statusText(status) === t("completed"); } export function avatarError(img) { img.addEventListener("error", () => { img.removeAttribute("src"); }); }