diff --git a/h5/KYC/index.html b/h5/KYC/index.html new file mode 100644 index 0000000..3ab7dad --- /dev/null +++ b/h5/KYC/index.html @@ -0,0 +1,13 @@ + + + + + + KYC + + + + + + + diff --git a/h5/admin-center/index.html b/h5/admin-center/index.html index 8d4f53d..04c7e47 100644 --- a/h5/admin-center/index.html +++ b/h5/admin-center/index.html @@ -1,35 +1,67 @@ - + - - - - - - - - - - Yumi H5 - - - - - + + + Admin Center + -
+
+ +
+
+
+
My available salary
+ +
+
$0
+
+
+
+
+ +
+ + diff --git a/h5/admin-policy/index.html b/h5/admin-policy/index.html new file mode 100644 index 0000000..aee1a9d --- /dev/null +++ b/h5/admin-policy/index.html @@ -0,0 +1,13 @@ + + + + + + Admin Policy + + + + + + + diff --git a/h5/agency-center/agency-center.css b/h5/agency-center/agency-center.css new file mode 100644 index 0000000..66dabbd --- /dev/null +++ b/h5/agency-center/agency-center.css @@ -0,0 +1,414 @@ +@font-face { + font-family: "Baloo 2"; + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Baloo 2"; + font-weight: 600; + font-style: normal; + font-display: swap; +} + +* { + 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%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; + overflow-x: hidden; +} + +body { + color: #21342a; + background: #edf8f1; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.agency-center { + min-height: 100vh; + overflow-y: auto; + background-color: #edf8f1; + background-image: none; +} + +.agency-center::-webkit-scrollbar { + display: none; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr auto; + align-items: center; + width: 100%; + 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; + font-size: 1.35em; + font-weight: 600; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #153d28; +} + +.header-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 6px; +} + +.icon-button, +.message-button, +.language-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: #1f6f46; +} + +.icon-button span { + display: block; + font-size: 2em; + line-height: 1; +} + +.message-button { + width: 36px; + border-radius: 50%; + background: #fff3c8; + color: #7d5b09; +} + +.message-button span { + width: 16px; + height: 16px; + display: block; + border-radius: 50%; + background: #f3c74e; + color: #5d4200; + font-size: 0.8em; + line-height: 16px; + text-align: center; +} + +.language-button { + min-width: 42px; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + color: #24724a; + font-size: 0.9em; +} + +.content { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px; +} + +.surface-card { + position: relative; + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #ffffff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.salary-card { + padding: 18px; + background: #f8fff9; + border-color: #94d9ae; + overflow: hidden; +} + +.salary-card::after { + content: ""; + position: absolute; + right: 18px; + bottom: 18px; + width: 64px; + height: 64px; + border-radius: 20px; + background: #d7f5e2; + border: 10px solid #ecfbf1; + opacity: 0.9; +} + +.salary-title { + position: relative; + z-index: 1; + display: flex; + justify-content: space-between; + gap: 8px; + margin-bottom: 5px; +} + +.salary-title span { + min-width: 0; + font-weight: 600; + color: #244735; +} + +.salary-title button { + flex-shrink: 0; + padding: 4px 10px; + border-radius: 999px; + background: #fff3c8; + color: #846216; + font-weight: 600; +} + +.salary-amount { + position: relative; + z-index: 1; + font-size: 1.5em; + font-weight: 600; + color: #105c36; +} + +.task-card { + margin: 0 1%; + padding: 16px; + background: #fffaf0; + border-color: #e7c777; +} + +.task-title { + margin-bottom: 12px; + font-weight: 700; + color: #3f3826; +} + +.task-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + text-align: center; +} + +.task-cell { + min-width: 0; + padding: 8px; + border-radius: 12px; + background: #fffdf7; + border: 1px solid #f0dfaa; +} + +.task-label { + margin-bottom: 5px; + font-size: 0.86em; + font-weight: 700; + color: #4b4536; +} + +.task-value { + font-size: 0.95em; + font-weight: 800; + color: #1f3528; + overflow-wrap: anywhere; +} + +.menu-card { + margin: 0 1% 12px; + padding: 10px; + background: #fbfffc; +} + +.menu-row { + width: 100%; + min-height: 4.5em; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-radius: 14px; + text-align: left; +} + +.menu-row:nth-of-type(4n + 1) { + background: #eefbf3; +} + +.menu-row:nth-of-type(4n + 3) { + background: #f7fbec; +} + +.menu-row:nth-of-type(4n + 5) { + background: #eef7fb; +} + +.menu-row:nth-of-type(4n + 7) { + background: #fff5ed; +} + +.menu-label { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 12px; +} + +.menu-label img, +.menu-symbol { + display: flex; + width: 2.7em; + padding: 0.55em; + aspect-ratio: 1 / 1; + flex-shrink: 0; + align-items: center; + justify-content: center; + border-radius: 14px; + background: #d7f5e2; + border: 1px solid #bce5ca; + color: #1e6b43; + font-weight: 800; +} + +.menu-row:nth-of-type(4n + 5) .menu-label img, +.menu-row:nth-of-type(4n + 5) .menu-symbol { + background: #dff2fb; + border-color: #c0e1f1; +} + +.menu-row:nth-of-type(4n + 7) .menu-label img, +.menu-row:nth-of-type(4n + 7) .menu-symbol { + background: #ffe6d6; + border-color: #ffd2b7; +} + +.menu-label span:last-child { + min-width: 0; + font-size: 1em; + font-weight: 600; + color: #244735; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-row > img { + display: block; + width: 1.35em; + flex-shrink: 0; + opacity: 0.5; +} + +.divider { + height: 1px; + margin: 0 12px; + background: #dbece0; +} + +.modal-mask { + position: fixed; + inset: 0; + z-index: 30; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(0, 0, 0, 0.5); +} + +.modal-dialog { + width: min(80vw, 320px); + padding: 14px; + border-radius: 12px; + background: #ffffff; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18); +} + +.modal-title { + color: rgba(0, 0, 0, 0.8); + font-weight: 700; + font-size: 1.2em; +} + +.modal-message { + color: rgba(0, 0, 0, 0.8); + font-weight: 600; + text-align: center; +} + +.modal-hint { + color: rgba(0, 0, 0, 0.45); + font-weight: 600; + font-size: 0.85em; + text-align: center; +} + +.modal-action { + width: 50%; + min-width: 120px; + padding: 8px 12px; + border-radius: 999px; + background: #f3c74e; + color: #3f2b00; + font-weight: 800; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 40; + 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"] .flip-img { + transform: rotate(180deg); +} diff --git a/h5/agency-center/agency-center.js b/h5/agency-center/agency-center.js new file mode 100644 index 0000000..d4879b6 --- /dev/null +++ b/h5/agency-center/agency-center.js @@ -0,0 +1,703 @@ +import { _ as policyIcon, a as transferIcon, b as bankIcon } from "../js/database-export-CGwyHyM--1776148658686.js"; +import { _ as coinsIcon, a as cashIcon } from "../js/database-dollar-DDq4bT-e-1776148658686.js"; +import { _ as arrowIcon } from "../js/arrow-Cf0JWzXk-1776148658686.js"; +import { _ as helpIcon } from "../js/help-BgsLqEJs-1776148658686.js"; + +const API_BASE_URL = "https://jvapi.haiyihy.com"; + +const labels = { + en: { + agency_center: "Agency Center", + my_available_salary: "My available salary", + my_salary_details: "Salary details", + today_host_task: "My task as a host today is:", + host_type: "Host type", + minutes: "Minutes", + gift_task: "Gift Task", + platform_policy: "Platform policy", + exchange_coins: "Exchange coins", + transfer: "Transfer", + cash_withdraw: "Cash withdraw", + bank_accounts: "Bank Accounts", + team_member: "Team Member", + team_bill: "Team Bill", + invite_members: "Invite Members", + prompt: "Prompt", + fill_info_tips: "Please complete your withdrawal information first.", + card_issue_tips: "Make sure the bank card and identity information are valid.", + to_improve: "To improve", + completed: "Completed", + in_progress: "In progress", + out_of_account: "Out of account", + failed_to_load_data: "Failed to load data. Please try again." + }, + ar: {}, + tr: {}, + bn: {} +}; + +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 state = { + lang: resolveLanguage(), + userProfile: null, + teamInfo: null, + identityInfo: null, + salaryInfo: { + currentSalary: "0.00" + }, + taskInfo: { + anchorType: "Chat", + minutesProgress: 0, + minutesTotal: 120, + giftTask: 0 + }, + memberQuantity: 0, + memberWorkTotal: "0.00", + teamBill: { + date: "–", + hostSalary: "–", + agentSalary: "–", + total: "–", + status: "–" + }, + bankCards: [], + defaultBankCard: null, + withdrawInfo: null, + withdrawEnabled: false, + showFillInfoModal: false +}; + +const rolePriority = { + guest: 0, + anchor: 1, + agent: 2, + bd: 3, + bdLeader: 4, + freightAgent: 5, + admin: 6 +}; + +const roleDefaultPages = { + admin: "/admin-center", + freightAgent: "/coin-seller", + agent: "/agency-center", + bdLeader: "/bd-leader-center", + bd: "/bd-center", + anchor: "/host-center", + guest: "/apply" +}; + +const rolePages = { + admin: [ + "/admin-center", + "/invite-bd-leader", + "/invite-bd", + "/invite-recharge-agency", + "/invite-agency", + "/item-distribution", + "/my-BDLeader-teams", + "/my-BD-teams", + "/my-agency-teams", + "/my-recharge-agency", + "/admin-policy" + ], + freightAgent: ["/coin-seller", "/seller-records", "/coin-seller-search"], + agent: [ + "/agency-center", + "/transfer", + "/exchange-gold-coins", + "/message", + "/platform-policy", + "/agency-setting", + "/team-member", + "/information-details", + "/history-salary", + "/invite-members", + "/team-bill" + ], + bdLeader: [ + "/bd-leader-center", + "/invite-bd-leader", + "/invite-bd", + "/bd-center", + "/transfer", + "/exchange-gold-coins", + "/message", + "/invite-agency", + "/bd-item-distribution", + "/team-member", + "/history-salary", + "/agency-list", + "/available-income", + "/income-details", + "/policy", + "/bd-list" + ], + bd: [ + "/bd-center", + "/transfer", + "/exchange-gold-coins", + "/message", + "/invite-agency", + "/bd-item-distribution", + "/team-member", + "/history-salary", + "/agency-list", + "/available-income", + "/income-details", + "/policy" + ], + anchor: [ + "/host-center", + "/transfer", + "/exchange-gold-coins", + "/message", + "/platform-policy", + "/host-setting", + "/information-details", + "/history-salary", + "/invite-members", + "/team-bill" + ], + guest: ["/apply", "/language"] +}; + +const publicPages = [ + "/apply", + "/language", + "/not_app", + "/pay-result", + "/map", + "/top-list", + "/weekly-star", + "/ranking", + "/games-king", + "/couple", + "/invitation-to-register", + "/invitation/invite-new-user", + "/recharge-reward", + "/login-reward", + "/activities/lesser-bairam", + "/activities/lucky-dollars-season3", + "/activities/spring-festival", + "/recharge", + "/recharge-pay-way", + "/recharge-guide", + "/recharge-agency-recruit", + "/search-payee", + "/cash-withdraw", + "/cash-out", + "/cash-out-details", + "/bank-cards", + "/bank-card", + "/KYC", + "/good-ID" +]; + +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 t(key) { + return labels[state.lang]?.[key] || labels.en[key] || key; +} + +function setLanguage() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase(); + document.querySelectorAll("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); +} + +function setIcons() { + const iconMap = { + policy: policyIcon, + transfer: transferIcon, + bank: bankIcon, + coins: coinsIcon, + cash: cashIcon, + arrow: arrowIcon, + help: helpIcon + }; + + document.querySelectorAll("[data-icon]").forEach((img) => { + img.src = iconMap[img.dataset.icon] || ""; + }); +} + +function isMobileDevice() { + return /Android|iPad|iPhone|iPod/.test(navigator.userAgent); +} + +function isAppEnvironment() { + return Boolean(window.app || window.webkit || window.FlutterPageControl); +} + +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: data["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 (!isAppEnvironment()) { + 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"); + } + }; + + if (isMobileDevice()) { + poll(); + return; + } + resolve(null); + }); +} + +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; +} + +function readCachedJson(key) { + const raw = localStorage.getItem(key); + if (!raw) return null; + try { + return JSON.parse(raw); + } catch (error) { + console.warn(`Failed to parse ${key}:`, error); + return null; + } +} + +function writeCachedJson(key, value) { + if (value === null || value === undefined) { + localStorage.removeItem(key); + return; + } + localStorage.setItem(key, JSON.stringify(value)); +} + +function getUserId() { + return state.userProfile?.id || readCachedJson("userProfile")?.id || null; +} + +function getTeamId() { + return state.teamInfo?.id || readCachedJson("teamInfo")?.id || null; +} + +function formatMoney(value) { + if (value === null || value === undefined || value === "") return "0"; + const number = Number(value); + if (Number.isNaN(number)) return String(value); + return number.toFixed(2); +} + +function formatBillStatus(status) { + return { + SETTLED: t("completed"), + UNPAID: t("in_progress"), + HANG_UP: t("out_of_account"), + PAY_OUT: t("completed") + }[status] || status || "–"; +} + +function getIdentityRoles(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) { + return roles.reduce((primary, role) => (rolePriority[role] > rolePriority[primary] ? role : primary), "guest"); +} + +function getAllowedPages(roles) { + const pages = new Set([...publicPages, "/loading"]); + roles.forEach((role) => { + (rolePages[role] || []).forEach((page) => pages.add(page)); + }); + return pages; +} + +function applyPermissionRedirect() { + const roles = getIdentityRoles(state.identityInfo); + const primaryRole = getPrimaryRole(roles); + const allowedPages = getAllowedPages(roles); + const currentPath = window.location.pathname.replace(/\/$/, "") || "/"; + + if (!allowedPages.has(currentPath)) { + navigateTo(roleDefaultPages[primaryRole] || "/apply"); + } +} + +async function fetchIdentity() { + const result = await apiGet("/app/h5/identity"); + if (result.status && result.body) state.identityInfo = result.body; +} + +async function fetchUserProfile() { + const result = await apiGet("/team/member/profile"); + if (result.status && result.body) { + state.userProfile = result.body; + writeCachedJson("userProfile", result.body); + } +} + +async function fetchTeamInfo() { + const result = await apiGet("/team/entry"); + if (result.status && result.body) { + state.teamInfo = result.body.teamProfile || result.body; + writeCachedJson("teamInfo", state.teamInfo); + } else { + state.teamInfo = null; + writeCachedJson("teamInfo", null); + } +} + +async function fetchBankBalance() { + const result = await apiGet("/wallet/bank/balance"); + if (result.status && typeof result.body === "number") { + state.salaryInfo.currentSalary = result.body.toFixed(2); + } else { + state.salaryInfo.currentSalary = "0"; + } +} + +async function fetchWorkStatistics() { + const userId = getUserId(); + if (!userId) return; + const result = await apiGet(`/team/member/work/statistics-now?dataType=DAILY&memberId=${encodeURIComponent(userId)}`); + if (result.status && result.body) { + const ownTime = Number(result.body.ownTime) || 0; + const otherTime = Number(result.body.otherTime) || 0; + const progress = ownTime + otherTime; + state.taskInfo.anchorType = "Chat"; + state.taskInfo.minutesProgress = Math.min(progress, state.taskInfo.minutesTotal); + state.taskInfo.giftTask = result.body.acceptGiftValue || 0; + } +} + +async function fetchMemberWorkTotal() { + const userId = getUserId(); + if (!userId) return; + const result = await apiGet(`/team/member-work?userId=${encodeURIComponent(userId)}`); + if (!result.status || !result.body) return; + + const targets = result.body.targets || []; + const isAgent = Boolean(state.identityInfo?.agent); + const isAnchor = Boolean(state.identityInfo?.anchor); + const total = targets.reduce((sum, item, index) => { + if (index === 0) return sum; + const settlement = item.target?.settlementResult || {}; + if (isAgent) { + return sum + Number(settlement.ownSalary || 0) + Number(settlement.memberSalary || 0); + } + if (isAnchor) { + return sum + Number(settlement.memberSalary || 0); + } + return sum; + }, 0); + state.memberWorkTotal = total.toFixed(2); +} + +async function fetchTeamMemberCount() { + const teamId = getTeamId(); + if (!teamId) return; + try { + const result = await apiGet(`/team/members/count?id=${encodeURIComponent(teamId)}`); + if (result.status && result.body) { + state.memberQuantity = result.body.memberQuantity || 0; + } + } catch (error) { + console.error("Failed to fetch team member count:", error); + state.memberQuantity = 87; + } +} + +async function fetchTeamBill() { + const teamId = getTeamId(); + if (!teamId) return; + const result = await apiGet(`/team/bill?id=${encodeURIComponent(teamId)}`); + if (!result.status || !Array.isArray(result.body) || !result.body[0]) return; + + const bill = result.body[0]; + const billBelong = String(bill.billBelong || ""); + state.teamBill = { + date: billBelong.length >= 6 ? `${billBelong.slice(0, 4)}.${billBelong.slice(4, 6)}` : "–", + hostSalary: bill.settleResult?.memberSalary?.toFixed(2) || "–", + agentSalary: bill.settleResult?.ownSalary?.toFixed(2) || "–", + total: bill.settleResult?.totalSalary?.toFixed(2) || "–", + status: formatBillStatus(bill.status) + }; +} + +async function fetchBankCards() { + const result = await apiGet("/wallet/bank-card/list"); + if (result.status && result.body) { + state.bankCards = result.body; + state.defaultBankCard = state.bankCards.find((card) => card.use) || null; + } +} + +async function fetchWithdrawInfo() { + const result = await apiGet("/wallet/withdraw-info/list"); + if (result.status && Array.isArray(result.body) && result.body.length > 0) { + const info = result.body[0]; + let previewUrls = []; + try { + previewUrls = JSON.parse(info.passportFrontUrl || "[]").map((item) => item); + } catch (error) { + console.warn("Failed to parse withdraw info preview urls:", error); + } + state.withdrawInfo = { ...info, previewUrls }; + } +} + +async function fetchWithdrawEnabled() { + const result = await apiGet("/wallet/bank/host-salary/withdraw/enabled"); + if (result.status) state.withdrawEnabled = Boolean(result.body); +} + +function render() { + document.getElementById("currentSalary").textContent = `$${state.salaryInfo.currentSalary}`; + document.getElementById("anchorType").textContent = state.taskInfo.anchorType; + document.getElementById("minutesProgress").textContent = state.taskInfo.minutesProgress; + document.getElementById("minutesTotal").textContent = state.taskInfo.minutesTotal; + document.getElementById("giftTask").textContent = state.taskInfo.giftTask; + + document.querySelectorAll(".cash-withdraw-row").forEach((node) => { + node.hidden = !state.withdrawEnabled; + }); + document.getElementById("fillInfoModal").hidden = !state.showFillInfoModal; +} + +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 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 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}`; +} + +function openCashWithdraw() { + if (!state.withdrawInfo || !state.defaultBankCard) { + state.showFillInfoModal = true; + render(); + return; + } + navigateTo("/cash-withdraw"); +} + +function bindEvents() { + const actions = { + back: closePage, + language: () => navigateTo("/language"), + message: () => navigateTo("/message"), + salaryDetails: () => navigateTo("/history-salary"), + platformPolicy: () => navigateTo("/platform-policy?from=agencycenter"), + exchangeCoins: () => navigateTo("/exchange-gold-coins"), + transfer: () => navigateTo("/transfer"), + cashWithdraw: openCashWithdraw, + bankAccounts: () => navigateTo("/agency-setting"), + teamMember: () => navigateTo(`/team-member?source=agency-center&members=${state.memberQuantity}`), + teamBill: () => navigateTo("/team-bill"), + inviteMembers: () => navigateTo("/invite-members"), + completeInfo: () => navigateTo("/agency-setting") + }; + + document.querySelectorAll("[data-action]").forEach((node) => { + const handler = actions[node.dataset.action]; + if (handler) node.addEventListener("click", handler); + }); + + document.getElementById("fillInfoModal").addEventListener("click", (event) => { + if (event.target.id === "fillInfoModal") { + state.showFillInfoModal = false; + render(); + } + }); +} + +async function initializeData() { + const firstWave = [ + fetchUserProfile(), + fetchTeamInfo(), + fetchBankBalance(), + fetchWithdrawEnabled(), + fetchBankCards(), + fetchWithdrawInfo(), + fetchIdentity() + ]; + const firstResults = await Promise.allSettled(firstWave); + firstResults.forEach((result) => { + if (result.status === "rejected") console.warn("Agency center data request failed:", result.reason); + }); + applyPermissionRedirect(); + + const secondWave = [ + fetchWorkStatistics(), + fetchTeamMemberCount(), + fetchTeamBill(), + fetchMemberWorkTotal() + ]; + const secondResults = await Promise.allSettled(secondWave); + secondResults.forEach((result) => { + if (result.status === "rejected") console.warn("Agency center dependent request failed:", result.reason); + }); +} + +async function init() { + setLanguage(); + setIcons(); + bindEvents(); + render(); + + localStorage.setItem("identity", JSON.stringify({ identity: "AGENCY" })); + localStorage.removeItem("payee"); + + try { + await connectToApp(); + await initializeData(); + } catch (error) { + console.warn("Agency center initialization failed:", error); + showToast(error.response?.errorMsg || error.message || t("failed_to_load_data")); + } finally { + render(); + } +} + +init(); diff --git a/h5/agency-center/index.html b/h5/agency-center/index.html index 668e516..c561167 100644 --- a/h5/agency-center/index.html +++ b/h5/agency-center/index.html @@ -13,23 +13,149 @@ - Yumi H5 - - - - - + Agency Center + -
+
+ + +
+
+
+ My available salary + +
+
$0.00
+
+ +
+
My task as a host today is:
+
+
+
Host type
+
Chat
+
+
+
Minutes
+
0/120
+
+
+
Gift Task
+
0
+
+
+
+ + +
+
+ + + + + diff --git a/h5/agency-list/index.html b/h5/agency-list/index.html new file mode 100644 index 0000000..c6abee8 --- /dev/null +++ b/h5/agency-list/index.html @@ -0,0 +1,13 @@ + + + + + + Agency List + + + + + + + diff --git a/h5/agency-setting/index.html b/h5/agency-setting/index.html new file mode 100644 index 0000000..fc40b16 --- /dev/null +++ b/h5/agency-setting/index.html @@ -0,0 +1,13 @@ + + + + + + Agency Setting + + + + + + + diff --git a/h5/apply/apply.css b/h5/apply/apply.css new file mode 100644 index 0000000..51e4225 --- /dev/null +++ b/h5/apply/apply.css @@ -0,0 +1,338 @@ +@font-face { + font-family: "Baloo 2"; + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Baloo 2"; + font-weight: 600; + font-style: normal; + font-display: swap; +} + +* { + 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%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; + overflow-x: hidden; +} + +body { + color: #25352b; + background: #edf8f1; +} + +button, +input { + font: inherit; +} + +button { + border: 0; + background: transparent; + color: inherit; +} + +.apply-view { + min-height: 100vh; + overflow-y: auto; + background: #edf8f1; +} + +.page-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 44px; + align-items: center; + min-height: calc(54px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 12px 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: #173a28; + font-size: 1.35em; + font-weight: 800; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.help-button { + width: 36px; + height: 36px; + justify-self: end; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: #fff3c8; + border: 1px solid #e9cc70; +} + +.help-button img { + width: 18px; + height: 18px; + display: block; +} + +.content { + display: flex; + flex-direction: column; + gap: 18px; + padding: 18px 12px; +} + +.reminder-section { + padding: 14px 10px 4px; +} + +.reminder-section h3 { + margin-bottom: 10px; + color: #788279; + font-size: 1.05em; + font-weight: 800; +} + +.reminder-section p { + color: #2f302d; + font-size: 1em; + font-weight: 700; +} + +.apply-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: stretch; +} + +.surface-card { + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #ffffff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.input-card { + min-height: 56px; + display: flex; + align-items: center; + gap: 10px; + padding: 0 14px; + background: #fffdf7; + border-color: #f0d97d; +} + +.input-card label { + flex-shrink: 0; + font-weight: 800; + color: #26352c; +} + +.input-card input { + min-width: 0; + width: 100%; + border: 0; + outline: 0; + background: transparent; + color: #27342d; + font-weight: 600; +} + +.input-card input::placeholder { + color: #8d9088; +} + +.apply-button { + min-width: 96px; + min-height: 56px; + padding: 0 22px; + border-radius: 999px; + background: #f3c74e; + color: #3f2b00; + font-weight: 800; + box-shadow: 0 8px 18px rgba(155, 119, 23, 0.18); +} + +.records-section { + padding: 16px; + border-radius: 16px; + background: #fbfffc; + border: 1px solid #cce9d7; +} + +.records-section h3 { + margin-bottom: 14px; + color: #173a28; + font-weight: 800; +} + +.record-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.record-card { + position: relative; + min-height: 74px; + display: grid; + grid-template-columns: auto 1fr auto; + gap: 10px; + align-items: center; + padding: 12px; +} + +.record-status { + color: #d54e42; + font-size: 0.9em; + font-weight: 800; +} + +.record-user { + min-width: 0; + display: flex; + align-items: center; + gap: 10px; +} + +.record-avatar { + width: 44px; + height: 44px; + flex-shrink: 0; + border-radius: 50%; + overflow: hidden; + background: #d7f5e2; + display: flex; + align-items: center; + justify-content: center; + color: #1e6b43; + font-weight: 800; +} + +.record-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.record-info { + min-width: 0; +} + +.record-name { + color: #25352b; + font-weight: 700; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.record-id { + color: #6d756f; + font-size: 0.82em; +} + +.record-cancel { + padding: 6px 12px; + border-radius: 999px; + background: #f3c74e; + color: #3f2b00; + font-weight: 800; +} + +.modal-layer { + position: fixed; + inset: 0; + z-index: 30; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(0, 0, 0, 0.5); +} + +.modal-card { + width: min(100%, 360px); + padding: 16px; + border-radius: 14px; + background: #ffffff; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18); +} + +.modal-card h3 { + color: rgba(0, 0, 0, 0.8); + font-weight: 800; + font-size: 1.3em; +} + +.modal-card p { + color: rgba(0, 0, 0, 0.8); + font-weight: 700; + font-size: 1.05em; + text-align: center; +} + +.modal-primary { + width: 80%; + padding: 8px 12px; + border-radius: 999px; + background: #f3c74e; + color: #3f2b00; + font-weight: 800; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 40; + 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; +} + +@media (max-width: 420px) { + .apply-row { + grid-template-columns: 1fr; + } + + .apply-button { + width: 100%; + } +} diff --git a/h5/apply/apply.js b/h5/apply/apply.js new file mode 100644 index 0000000..630a3a3 --- /dev/null +++ b/h5/apply/apply.js @@ -0,0 +1,511 @@ +import { _ as helpIcon } from "../js/help-BgsLqEJs-1776148658686.js"; + +const API_BASE_URL = "https://jvapi.haiyihy.com"; + +const labels = { + en: { + apply_join_team: "Apply to join the team", + apply_reminder: "Reminder before application:", + apply_reminder_detail: + "Enter the ID of your agent, and you will become the host after the host apply is approved. After becoming an hoster,your income will be entrusted to your agent", + apply_id: "Apply ID:", + enter_agent_id: "Please enter the agent ID", + apply: "Apply", + application_records: "Application records", + pending: "Pending", + cancel: "Cancel", + application_help: "Application help", + application_help_detail: "Enter your agent ID and wait for approval.", + got_it: "Got it", + user_id_prefix: "ID:", + application_submitted: "Application submitted", + application_cancelled: "Application cancelled", + cancellation_failed: "Cancellation failed", + application_record_not_found: "Application record not found", + application_processed_cannot_cancel: "Application processed, cannot cancel", + network_error: "Network error", + cancellation_failed_try_again: "Cancellation failed, please try again" + }, + ar: {}, + tr: {}, + bn: {} +}; + +class ApiError extends Error { + constructor(message, options = {}) { + super(message); + this.name = "ApiError"; + this.type = options.type || "unknown"; + this.status = options.status || null; + this.errorCode = options.errorCode || null; + this.errorCodeName = options.errorCodeName || null; + this.errorMsg = options.errorMsg || null; + this.response = options.response || null; + } +} + +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 state = { + lang: resolveLanguage(), + agencyId: "", + inlineError: "", + loading: false, + records: [], + selectedTeam: null, + helpOpen: false +}; + +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 t(key) { + return labels[state.lang]?.[key] || labels.en[key] || key; +} + +function setLanguage() { + 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); + }); + document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => { + node.setAttribute("placeholder", t(node.dataset.i18nPlaceholder)); + }); +} + +function setIcons() { + document.getElementById("helpIcon").src = helpIcon; +} + +function isMobileDevice() { + return /Android|iPad|iPhone|iPod/.test(navigator.userAgent); +} + +function isAppEnvironment() { + return Boolean(window.app || window.webkit || window.FlutterPageControl); +} + +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) { + try { + const data = JSON.parse(raw); + const appIntel = splitHeaderPairs(data["Req-App-Intel"]); + const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); + + return { + success: true, + data: { + 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 + } + }; + } catch (error) { + console.error("Failed to parse access origin:", error); + return { success: false, error: "accessOrigin Error.", data: null }; + } +} + +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 (!isAppEnvironment()) { + 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"); + } + }; + + if (isMobileDevice()) { + poll(); + return; + } + resolve(null); + }); +} + +async function connectToApp() { + const raw = await requestAccessOrigin(); + if (!raw) return { success: true, environment: "browser" }; + const parsed = parseAccessOrigin(raw); + if (!parsed.success) { + navigateTo(`/not_app?message=${encodeURIComponent(parsed.error)}`); + return { success: false, error: parsed.error }; + } + setHeadersFromApp(parsed.data); + return { success: true, environment: "app" }; +} + +async function apiRequest(path, options = {}) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method: options.method || "GET", + headers: { + ...commonHeaders, + ...(options.body ? { "Content-Type": "application/json" } : {}) + }, + body: options.body ? JSON.stringify(options.body) : undefined + }); + + let data = {}; + try { + data = await response.json(); + } catch { + data = {}; + } + + if (!response.ok) { + throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, { + type: "http", + status: response.status, + response: data, + errorCode: data.errorCode, + errorCodeName: data.errorCodeName, + errorMsg: data.errorMsg + }); + } + + if (data.status === false) { + throw new ApiError(data.errorMsg || data.message || `API Error: ${data.errorCode}`, { + type: "business", + status: response.status, + response: data, + errorCode: data.errorCode, + errorCodeName: data.errorCodeName, + errorMsg: data.errorMsg + }); + } + + return data; +} + +function apiGet(path) { + return apiRequest(path); +} + +function apiPost(path, body) { + return apiRequest(path, { method: "POST", body }); +} + +async function fetchUserProfile() { + const result = await apiGet("/team/member/profile"); + if (result.status && result.body) { + localStorage.setItem("userProfile", JSON.stringify(result.body)); + } +} + +async function fetchApplicationRecord() { + try { + const result = await apiGet("/team/wait-apply/profile"); + state.records = result.status && result.body ? [result.body] : []; + } catch (error) { + console.error("获取申请记录失败:", error); + state.records = []; + showToast(error.errorMsg || error.message || ""); + } + render(); +} + +async function cancelApplication(record) { + try { + const result = await apiPost("/team/user/join-apply-cancel", { id: record.messageId }); + if (result.status) { + showToast(t("application_cancelled")); + state.records = []; + await fetchApplicationRecord(); + } else { + showToast(result.message || t("cancellation_failed")); + } + } catch (error) { + console.error("撤销申请失败:", error); + if (error instanceof ApiError) { + if (error.type === "business") { + switch (error.errorCode) { + case 6404: + showToast(t("application_record_not_found")); + break; + case 6003: + showToast(t("application_processed_cannot_cancel")); + break; + default: + showToast(error.errorMsg || error.message || "Cancellation failed, please try again"); + } + } else { + showToast(error.type === "http" ? t("network_error") : t("cancellation_failed")); + } + return; + } + showToast(t("cancellation_failed_try_again")); + } +} + +async function searchTeamAccount(account) { + try { + console.debug("🔍 Searching team account:", account); + const result = await apiGet(`/team/search-account?account=${encodeURIComponent(account)}`); + if (result.status && result.body && result.body.teamProfile) { + state.selectedTeam = result.body; + console.debug("✅ Team found:", result.body.teamProfile); + return result.body.teamProfile.id; + } + state.selectedTeam = null; + showToast(result.errorMsg || ""); + } catch (error) { + state.selectedTeam = null; + showToast(error.errorMsg || error.message || ""); + } + return null; +} + +async function submitApplication() { + if (!state.agencyId.trim()) { + state.inlineError = "Please enter agency ID"; + return; + } + + state.loading = true; + state.inlineError = ""; + render(); + + try { + const teamId = await searchTeamAccount(state.agencyId.trim()); + if (!teamId) { + state.inlineError = "Account not found or not an agency"; + return; + } + + console.debug("✅ Team ID found:", teamId); + console.debug("📝 Step 2: Submitting application..."); + const result = await apiPost("/team/user/apply", { teamId, reason: "JOIN" }); + if (result.status) { + showToast(t("application_submitted")); + await fetchApplicationRecord(); + state.agencyId = ""; + state.selectedTeam = null; + } else { + state.inlineError = result.message || "Application failed. Please try again."; + } + } catch (error) { + if (error instanceof ApiError) { + if (error.type === "business") { + showToast(`business error!${error.errorMsg || ""}`); + } else if (error.type === "http") { + showToast(error.status === 406 ? error.errorMsg : error.message || "Unknown error, please try again"); + } else { + showToast("Unknown error, please try again"); + } + } + } finally { + state.loading = false; + render(); + } +} + +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}`; +} + +function avatarText(profile) { + const name = profile?.userNickname || profile?.account || "U"; + return name.trim().slice(0, 1).toUpperCase() || "U"; +} + +function userAccount(profile) { + return profile?.ownSpecialId?.account || profile?.account || ""; +} + +function renderRecords() { + const recordsSection = document.getElementById("recordsSection"); + const recordList = document.getElementById("recordList"); + recordList.innerHTML = ""; + recordsSection.hidden = state.records.length === 0; + document.querySelector("[data-action='apply']").hidden = state.records.length > 0; + + state.records.forEach((record) => { + const profile = record.ownUserProfile || {}; + const card = document.createElement("article"); + card.className = "surface-card record-card"; + card.innerHTML = ` +
+
+
+
+
+
+
+
+ + `; + + card.querySelector(".record-status").textContent = t("pending"); + const img = card.querySelector(".record-avatar img"); + const fallback = card.querySelector(".record-avatar span"); + img.src = profile.userAvatar || ""; + fallback.textContent = avatarText(profile); + img.addEventListener("error", () => { + img.hidden = true; + fallback.hidden = false; + }); + if (!profile.userAvatar) { + img.hidden = true; + fallback.hidden = false; + } else { + fallback.hidden = true; + } + card.querySelector(".record-name").textContent = profile.userNickname || ""; + card.querySelector(".record-id").textContent = `${t("user_id_prefix")} ${userAccount(profile)}`; + const cancelButton = card.querySelector(".record-cancel"); + cancelButton.textContent = t("cancel"); + cancelButton.addEventListener("click", () => cancelApplication(record)); + recordList.appendChild(card); + }); +} + +function render() { + const input = document.getElementById("agencyInput"); + if (document.activeElement !== input) input.value = state.agencyId; + document.querySelector("[data-action='apply']").textContent = state.loading ? "..." : t("apply"); + document.getElementById("helpModal").hidden = !state.helpOpen; + renderRecords(); +} + +function showToast(message) { + if (!message) return; + const toast = document.getElementById("toast"); + toast.textContent = message; + toast.hidden = false; + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => { + toast.hidden = true; + }, 2500); +} + +function bindEvents() { + const input = document.getElementById("agencyInput"); + input.addEventListener("input", () => { + state.agencyId = input.value; + render(); + }); + + document.querySelector("[data-action='apply']").addEventListener("click", submitApplication); + document.querySelector("[data-action='help']").addEventListener("click", () => { + state.helpOpen = true; + render(); + }); + document.querySelector("[data-action='close-help']").addEventListener("click", () => { + state.helpOpen = false; + render(); + }); + document.getElementById("helpModal").addEventListener("click", (event) => { + if (event.target.id === "helpModal") { + state.helpOpen = false; + render(); + } + }); +} + +async function initializePageData() { + try { + await fetchUserProfile(); + } catch (error) { + console.error("❌ Data initialization failed:", error); + } + await fetchApplicationRecord(); +} + +async function init() { + setLanguage(); + setIcons(); + bindEvents(); + render(); + + try { + const connection = await connectToApp(); + if (connection.success) await initializePageData(); + } catch (error) { + console.error("❌ Page initialization failed:", error); + } +} + +init(); diff --git a/h5/apply/index.html b/h5/apply/index.html index 2d7c08d..4365f01 100644 --- a/h5/apply/index.html +++ b/h5/apply/index.html @@ -13,23 +13,51 @@ - Yumi H5 - - - - - + Apply + -
+
+ + +
+
+

Reminder before application:

+

+ Enter the ID of your agent, and you will become the host after the host apply is approved. After becoming an hoster,your income will be entrusted to your agent +

+
+ +
+
+ + +
+ +
+ + +
+
+ + + + + diff --git a/h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png b/h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png index 59aa5a3..3705cf9 100644 Binary files a/h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png and b/h5/assets/defaultAvatar-CdxWBK1k-1776148661448.png differ diff --git a/h5/available-income/index.html b/h5/available-income/index.html new file mode 100644 index 0000000..b07f2ec --- /dev/null +++ b/h5/available-income/index.html @@ -0,0 +1,13 @@ + + + + + + Available Income + + + + + + + diff --git a/h5/bank-card/index.html b/h5/bank-card/index.html new file mode 100644 index 0000000..bd1f6a8 --- /dev/null +++ b/h5/bank-card/index.html @@ -0,0 +1,13 @@ + + + + + + Bank Card + + + + + + + diff --git a/h5/bank-cards/index.html b/h5/bank-cards/index.html new file mode 100644 index 0000000..f854e32 --- /dev/null +++ b/h5/bank-cards/index.html @@ -0,0 +1,13 @@ + + + + + + Bank Cards + + + + + + + diff --git a/h5/bd-center/bd-center.css b/h5/bd-center/bd-center.css index 12e14e3..241fbec 100644 --- a/h5/bd-center/bd-center.css +++ b/h5/bd-center/bd-center.css @@ -248,7 +248,7 @@ button { .menu-label span { min-width: 0; - font-size: 1em; + font-size: 1.6em; font-weight: 600; color: #244735; overflow: hidden; diff --git a/h5/bd-center/bd-center.js b/h5/bd-center/bd-center.js index 78cd962..56137fa 100644 --- a/h5/bd-center/bd-center.js +++ b/h5/bd-center/bd-center.js @@ -69,7 +69,12 @@ let commonHeaders = { const state = { lang: resolveLanguage(), balance: 0, - hasGiftPermission: false + hasGiftPermission: false, + appConnected: false, + userInfo: null, + teamInfo: null, + identityInfo: null, + identity: "BD" }; function resolveLanguage() { @@ -140,7 +145,7 @@ function isMobileDevice() { } function isAppEnvironment() { - return Boolean(window.app || window.webkit || window.FlutterPageControl); + return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp); } function splitHeaderPairs(value) { @@ -166,9 +171,9 @@ function parseAccessOrigin(raw) { buildVersion: appIntel.build, appVersion: appIntel.version, appChannel: appIntel.channel, - reqImei: appIntel["Req-Imei"], + reqImei: data["Req-Imei"] || appIntel["Req-Imei"], sysOrigin: sysOrigin.origin, - sysOriginChild: sysOrigin.child + sysOriginChild: sysOrigin.originChild || sysOrigin.child }; } @@ -186,7 +191,7 @@ function setHeadersFromApp(headers) { if (headers.reqImei) next["req-imei"] = headers.reqImei; if (headers.sysOrigin) { const origin = [`origin=${headers.sysOrigin}`]; - if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); + if (headers.sysOriginChild) origin.push(`originChild=${headers.sysOriginChild}`); next["req-sys-origin"] = origin.join(";"); } commonHeaders = { ...commonHeaders, ...next }; @@ -229,7 +234,7 @@ function requestAccessOrigin() { } }; - if (isMobileDevice()) { + if (isMobileDevice() || isAppEnvironment()) { poll(); } else { resolve(null); @@ -239,8 +244,12 @@ function requestAccessOrigin() { async function connectToApp() { const raw = await requestAccessOrigin(); - if (!raw) return; + if (!raw) { + state.appConnected = true; + return; + } setHeadersFromApp(parseAccessOrigin(raw)); + state.appConnected = true; } async function apiGet(path) { @@ -272,6 +281,59 @@ async function fetchGiftPermission() { } } +async function fetchUserProfile() { + const result = await apiGet("/team/member/profile"); + if (result.status && result.body) { + state.userInfo = result.body; + } +} + +async function fetchTeamInfo() { + const result = await apiGet("/team/entry"); + if (result.status && result.body) { + state.teamInfo = result.body.teamProfile || result.body; + } +} + +async function fetchIdentity() { + const result = await apiGet("/app/h5/identity"); + if (result.status && result.body) { + state.identityInfo = result.body; + } +} + +async function initializePageConfig() { + const results = await Promise.allSettled([fetchUserProfile(), fetchTeamInfo(), fetchIdentity()]); + results.forEach((result) => { + if (result.status === "rejected") { + console.warn("BD center page config request failed:", result.reason); + } + }); +} + +function clearPayeeStore() { + try { + sessionStorage.removeItem("payee"); + localStorage.removeItem("payee"); + } catch (error) { + console.debug("BD center payee cache clear skipped:", error); + } +} + +function setIdentity(identity) { + state.identity = identity; + localStorage.setItem("identity", JSON.stringify({ identity })); +} + +async function loadBDCenterData() { + const results = await Promise.allSettled([fetchBalance(), fetchGiftPermission()]); + results.forEach((result) => { + if (result.status === "rejected") { + console.warn("BD center data request failed:", result.reason); + } + }); +} + function navigateTo(target) { const url = new URL(target, window.location.origin); const currentLang = new URLSearchParams(window.location.search).get("lang"); @@ -310,16 +372,13 @@ async function init() { setIcons(); bindEvents(); render(); - localStorage.setItem("identity", JSON.stringify({ identity: "BD" })); + clearPayeeStore(); + setIdentity("BD"); try { await connectToApp(); - const results = await Promise.allSettled([fetchBalance(), fetchGiftPermission()]); - results.forEach((result) => { - if (result.status === "rejected") { - console.warn("BD center data request failed:", result.reason); - } - }); + await initializePageConfig(); + await loadBDCenterData(); } catch (error) { console.warn("BD center initialization failed:", error); showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance")); diff --git a/h5/bd-item-distribution/index.html b/h5/bd-item-distribution/index.html new file mode 100644 index 0000000..8bb6e2d --- /dev/null +++ b/h5/bd-item-distribution/index.html @@ -0,0 +1,13 @@ + + + + + + BD Item Distribution + + + + + + + diff --git a/h5/bd-leader-center/bd-leader-center.css b/h5/bd-leader-center/bd-leader-center.css new file mode 100644 index 0000000..89056f7 --- /dev/null +++ b/h5/bd-leader-center/bd-leader-center.css @@ -0,0 +1,482 @@ +@font-face { + font-family: "Baloo 2"; + font-weight: 400 700; + font-style: normal; + font-display: swap; +} + +* { + 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%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; + overflow-x: hidden; +} + +body { + color: #1f2937; + background: #f4fbf6; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.bd-center { + min-height: 100vh; + overflow-y: auto; + background: #f4fbf6; +} + +.general-header { + position: sticky; + top: 0; + z-index: 10; + display: grid; + grid-template-columns: 44px 1fr 58px; + align-items: center; + width: 100%; + min-height: calc(48px + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 10px 0; + background: #ffffff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.general-header h1 { + min-width: 0; + color: #111827; + font-size: 1.35em; + font-weight: 700; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.header-button, +.language-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: #111827; + font-weight: 700; +} + +.header-button span { + display: block; + font-size: 2em; + line-height: 1; +} + +.language-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #e9f7ee; + color: #276344; + font-size: 0.86em; +} + +.content { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} + +.gradient-border { + position: relative; + overflow: hidden; + border-radius: 20px; + background: #ffffff; + border: 1px solid #d7eadf; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.salary-card { + margin-bottom: 12px; +} + +.salary-inner { + padding: 16px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.salary-row { + display: flex; + justify-content: space-between; + gap: 10px; + margin-bottom: 5px; +} + +.salary-title { + flex: 1; + min-width: 0; + font-weight: 600; + color: #1f2937; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.salary-link { + flex-shrink: 0; + max-width: 48%; + color: rgba(0, 0, 0, 0.4); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.salary-amount { + color: #1f2937; + font-size: 1.5em; + font-weight: 600; + direction: ltr; + display: flex; + align-items: center; + gap: 8px; +} + +.salary-icon { + display: block; + width: 1.35em; + aspect-ratio: 1 / 1; + object-fit: contain; + flex-shrink: 0; +} + +.menu-card { + margin: 0 1% 12px; +} + +.menu-inner { + padding: 16px; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 4vw; +} + +.menu-row { + width: 100%; + min-height: 1.8em; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + text-align: left; +} + +.menu-label { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 1vw; +} + +.menu-label img { + display: block; + width: 1.6em; + aspect-ratio: 1 / 1; + object-fit: contain; + flex-shrink: 0; +} + +.menu-label span { + min-width: 0; + color: #1f2937; + font-size: 1.4em; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.arrow { + display: block; + width: 1.5em; + aspect-ratio: 1 / 1; + object-fit: contain; + flex-shrink: 0; +} + +.divider { + border-bottom: 1px solid #e6e6e6; +} + +.mask-layer { + position: fixed; + inset: 0; + z-index: 40; + background: rgba(0, 0, 0, 0.35); +} + +.mask-hit { + position: absolute; + inset: 0; +} + +.sheet { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 41; + max-height: 80vh; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 10px; + padding: 20px; + border-radius: 16px 16px 0 0; + background: #ffffff; +} + +.details-sheet { + max-height: 60vh; +} + +.sheet-title { + display: flex; + justify-content: center; + align-items: center; + color: #333333; + font-size: 1em; + font-weight: 600; +} + +.summary-box, +.details-summary { + display: grid; + gap: 4px; + margin: 0 4px; + padding: 12px; + border-radius: 8px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + font-size: 0.9em; + font-weight: 600; +} + +.details-summary { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.7em; +} + +.salary-list, +.member-list { + display: flex; + flex-direction: column; + gap: 8px; + overflow-y: auto; +} + +.salary-item { + position: relative; + width: 100%; + border-radius: 8px; + background: #ebf1fd; +} + +.salary-item.is-pending { + background: #feebef; +} + +.salary-item.is-completed { + background: #e8faed; +} + +.salary-item.is-out { + background: #e9e9e9; +} + +.status-background { + position: absolute; + top: 0; + left: 0; + min-width: 7.5em; + max-width: 9em; + padding: 2px 8px; + border-radius: 8px 0 8px 0; + background: #3d73ff; + color: #ffffff; + font-size: 0.9em; + font-weight: 600; + text-align: center; +} + +.is-pending .status-background { + background: #ff3d40; +} + +.is-completed .status-background { + background: #3dff54; +} + +.is-out .status-background { + background: #aeaeae; + font-size: 0.7em; +} + +.salary-item-content { + display: grid; + gap: 7px; + padding: 32px 8px 30px; + font-size: 0.9em; +} + +.pair-line { + display: flex; + justify-content: space-between; + gap: 8px; +} + +.pair-line > div { + min-width: 0; +} + +.more-bt { + position: absolute; + right: 8px; + bottom: 6px; + color: #4f46e5; + font-size: 0.9em; + font-weight: 700; +} + +.member-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; + padding: 10px 16px; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.member-item img { + width: 3.5em; + aspect-ratio: 1 / 1; + border-radius: 50%; + object-fit: cover; + background: #eef2f7; +} + +.member-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.member-info div:first-child { + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-info div:last-child { + color: rgba(0, 0, 0, 0.4); + font-size: 0.8em; + font-weight: 500; +} + +.member-metrics { + width: auto; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: space-around; + font-size: 0.8em; + font-weight: 500; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(28px + env(safe-area-inset-bottom)); + z-index: 50; + max-width: 86vw; + transform: translateX(-50%); + padding: 10px 14px; + border-radius: 8px; + color: #ffffff; + background: rgba(0, 0, 0, 0.75); + font-size: 13px; + font-weight: 500; + text-align: center; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .flip-img { + transform: scaleX(-1); +} + +[dir="rtl"] .menu-row { + text-align: right; +} + +[dir="rtl"] .status-background { + left: auto; + right: 0; + border-radius: 0 8px 0 8px; +} + +[dir="rtl"] .more-bt { + right: auto; + left: 8px; +} + +@media screen and (max-width: 360px) { + .bd-center { + font-size: 10px; + } +} + +@media screen and (min-width: 360px) { + .bd-center { + font-size: 12px; + } +} + +@media screen and (min-width: 768px) { + .bd-center { + font-size: 24px; + } +} + +@media screen and (min-width: 1024px) { + .bd-center { + font-size: 32px; + } +} diff --git a/h5/bd-leader-center/bd-leader-center.js b/h5/bd-leader-center/bd-leader-center.js new file mode 100644 index 0000000..d26799e --- /dev/null +++ b/h5/bd-leader-center/bd-leader-center.js @@ -0,0 +1,558 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; + +const labels = { + en: { + bd_leader_center: "BD Leader Center", + my_available_salary: "My available salary", + my_salary_link: "Details", + bd_policy_link: "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", + send_welcome_gift: "Send welcome gift", + bd_salary: "BD salary", + bd_leader_history: "BD Leader history", + total_income: "Total income", + previous_period_income: "Previous period income", + agency_number: "Agency number", + bd_number: "BD number", + team_salary: "Team salary", + team_top_up: "Team top up", + bd_income_salary: "BD income salary", + bd_income_top_up: "BD income top up", + user_id_prefix: "ID: ", + salary: "Salary", + recharge: "Recharge", + host: "Host", + agencies: "Agencies", + total_team_salary: "Total team salary", + more: "More", + in_progress: "In Progress", + pending: "Pending", + completed: "Completed", + out_of_account: "Out of account", + failed_to_load_balance: "Failed to load balance. Please try again.", + failed_to_load_history: "Failed to load history. Please try again.", + request_not_available: "Request failed. Please try again." + }, + ar: {}, + tr: {}, + bn: {} +}; + +["ar", "tr", "bn"].forEach((lang) => { + labels[lang] = { ...labels.en }; +}); + +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 state = { + lang: resolveLanguage(), + balanceTotal: {}, + userInfo: null, + teamInfo: null, + identityInfo: null, + identity: "BD_LEADER", + modalMode: "", + historyType: "bd", + bdHistory: {}, + bdLeaderHistory: {}, + historyMore: {} +}; + +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 t(key) { + return labels[state.lang]?.[key] || labels.en[key] || key; +} + +function setLanguage() { + document.documentElement.lang = state.lang; + document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; + document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase(); + document.querySelectorAll("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); +} + +function setIcons() { + const icon = (body) => `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(` + + ${body} + + `)}`; + + const iconMap = { + policy: icon(` + + + + `), + bank: icon(` + + + + `), + gift: icon(` + + + + + `), + invite: icon(` + + + + `), + arrow: icon(` + + `), + salary: icon(` + + + + `) + }; + + document.querySelectorAll("[data-icon]").forEach((img) => { + img.src = iconMap[img.dataset.icon] || ""; + }); +} + +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 isMobileDevice() { + return /Android|iPad|iPhone|iPod/.test(navigator.userAgent); +} + +function isAppEnvironment() { + return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp); +} + +function requestAccessOrigin() { + return new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + + if (!isAppEnvironment()) { + 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"); + } + }; + + if (isMobileDevice() || isAppEnvironment()) { + poll(); + } else { + resolve(null); + } + }); +} + +async function connectToApp() { + const raw = await requestAccessOrigin(); + if (!raw) return { success: true, environment: "browser" }; + setHeadersFromApp(parseAccessOrigin(raw)); + return { success: true, environment: "app" }; +} + +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 result = await apiGet("/team/member/profile"); + if (result.status && result.body) state.userInfo = result.body; +} + +async function fetchTeamInfo() { + try { + const result = await apiGet("/team/entry"); + if (result.status && result.body) state.teamInfo = result.body.teamProfile || result.body; + } catch (error) { + state.teamInfo = null; + console.debug("BD leader team info unavailable:", error); + } +} + +async function fetchIdentity() { + const result = await apiGet("/app/h5/identity"); + if (result.status && result.body) state.identityInfo = result.body; +} + +async function fetchBalanceTotal() { + const result = await apiGet("/wallet/salary-account/balance/total"); + if (result.status && result.body) state.balanceTotal = result.body || {}; +} + +async function fetchBDHistoryMore(billBelong) { + const result = await apiGet(`/team/bd/history/more?billBelong=${encodeURIComponent(billBelong)}`); + if (result.status && result.body) state.historyMore = result.body || {}; +} + +async function fetchBDLeaderHistoryMore(billBelong) { + const result = await apiGet(`/team/bd/leader/history/more?billBelong=${encodeURIComponent(billBelong)}`); + if (result.status && result.body) state.historyMore = result.body || {}; +} + +function setIdentity(identity) { + state.identity = identity; + localStorage.setItem("identity", JSON.stringify({ identity })); +} + +function clearPayee() { + try { + sessionStorage.removeItem("payee"); + localStorage.removeItem("payee"); + } catch (error) { + console.debug("Payee cache clear skipped:", error); + } +} + +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 normalizeStatus(status) { + if (status === "In Progress" || status === t("in_progress")) return t("in_progress"); + if (status === "Pending" || status === t("pending")) return t("pending"); + if (status === "Completed" || status === t("completed")) return t("completed"); + if (status === "Out of account" || status === t("out_of_account")) return t("out_of_account"); + return status || ""; +} + +function statusClass(status) { + const value = normalizeStatus(status); + if (value === t("completed")) return "is-completed"; + if (value === t("pending")) return "is-pending"; + if (value === t("out_of_account")) return "is-out"; + return "is-progress"; +} + +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}`; +} + +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 renderBalance() { + document.getElementById("availableBalance").textContent = `$${formatMoney(state.balanceTotal.availableBalance || 0)}`; +} + +function historySource() { + return state.historyType === "bd" ? state.bdHistory : state.bdLeaderHistory; +} + +function historyTitle() { + return state.historyType === "bd" ? t("bd_salary") : t("bd_leader_history"); +} + +function renderHistorySheet() { + const data = historySource(); + document.getElementById("historyTitle").textContent = historyTitle(); + document.getElementById("historyTotalIncome").textContent = formatMoney(data.totalIncome || 0); + document.getElementById("historyPreviousIncome").textContent = formatMoney(data.previousPeriodIncome || 0); + + const list = document.getElementById("historyList"); + list.innerHTML = ""; + const items = data.incomeDetails || []; + items.forEach((item) => list.appendChild(createHistoryItem(item))); +} + +function createHistoryItem(item) { + const card = document.createElement("article"); + card.className = `salary-item ${statusClass(item.statusText)}`; + + const status = document.createElement("div"); + status.className = "status-background"; + status.textContent = normalizeStatus(item.statusText); + card.appendChild(status); + + const content = document.createElement("div"); + content.className = "salary-item-content"; + if (state.historyType === "bd") { + content.append( + textLine(item.billTitle || ""), + textLine(`${t("agency_number")}: ${item.agencyNumber || 0}`), + pairLine(`${t("team_salary")}: $${item.teamSalaryAmount || 0}`, `${t("bd_income_salary")}: $${item.bdIncomeSalary || item.settlementSalary || 0} (${item.bdRatioSalary || item.commissionRate || 0}%)`), + pairLine(`${t("team_top_up")}: $${item.teamRechargeAmount || 0}`, `${t("bd_income_top_up")}: $${item.bdIncomeRecharge || item.settlementSalary || 0} (${item.bdRatioRecharge || item.commissionRate || 0}%)`) + ); + } else { + content.append( + textLine(item.billTitle || ""), + pairLine(`${t("bd_number")}: ${item.bdNumber || 0}`, `${t("team_salary")}: $${item.teamSalaryAmount || 0}`) + ); + } + card.appendChild(content); + + if (normalizeStatus(item.statusText) === t("completed")) { + const more = document.createElement("button"); + more.type = "button"; + more.className = "more-bt"; + more.textContent = t("more"); + more.addEventListener("click", () => showHistoryMore(item.billBelong)); + card.appendChild(more); + } + return card; +} + +function textLine(text) { + const node = document.createElement("div"); + node.textContent = text; + return node; +} + +function pairLine(left, right) { + const node = document.createElement("div"); + node.className = "pair-line"; + const leftNode = document.createElement("div"); + const rightNode = document.createElement("div"); + leftNode.textContent = left; + rightNode.textContent = right; + node.append(leftNode, rightNode); + return node; +} + +async function showHistoryMore(billBelong) { + try { + if (state.historyType === "bd") { + await fetchBDHistoryMore(billBelong); + } else { + await fetchBDLeaderHistoryMore(billBelong); + } + state.modalMode = "details"; + renderModal(); + } catch (error) { + console.warn("BD leader history more failed:", error); + showToast(error.response?.errorMsg || error.message || t("failed_to_load_history")); + } +} + +function renderDetailsSheet() { + const data = state.historyMore || {}; + document.getElementById("detailsPeriod").textContent = data.period || ""; + const summary = document.getElementById("detailsSummary"); + summary.innerHTML = ""; + if (state.historyType === "bd") { + summary.append( + textLine(`${t("team_top_up")}: $${data.teamTotalRecharge || 0}`), + textLine(`${t("total_team_salary")}: $${data.teamTotalSalary || 0}`) + ); + } else { + summary.append( + textLine(`${t("team_salary")}: $${data.teamSalary || 0}`), + textLine(`${t("bd_number")}: ${data.bdNumber || 0}`) + ); + } + + const list = document.getElementById("memberDetails"); + list.innerHTML = ""; + (data.memberDetails || []).forEach((item) => { + const card = document.createElement("article"); + card.className = "member-item"; + const img = document.createElement("img"); + img.src = state.historyType === "bd" ? item.userAvatar || "" : item.bdUserAvatar || ""; + img.alt = ""; + img.addEventListener("error", () => { + img.removeAttribute("src"); + }); + const info = document.createElement("div"); + info.className = "member-info"; + info.append( + textLine(state.historyType === "bd" ? item.userName || "" : item.bdUserName || ""), + textLine(`${t("user_id_prefix")}${item.account || ""}`) + ); + const metrics = document.createElement("div"); + metrics.className = "member-metrics"; + metrics.append(textLine(`${t("salary")}: $${item.salary || 0}`)); + if (state.historyType === "bd") { + metrics.append(textLine(`${t("recharge")}: $${item.recharge || 0}`), textLine(`${t("host")}: ${item.hostCount || 0}`)); + } else { + metrics.append(textLine(`${t("agencies")}: ${item.agencyCount || 0}`)); + } + card.append(img, info, metrics); + list.appendChild(card); + }); +} + +function renderModal() { + const mask = document.getElementById("maskLayer"); + const history = document.getElementById("historySheet"); + const details = document.getElementById("detailsSheet"); + const showHistory = state.modalMode === "history"; + const showDetails = state.modalMode === "details"; + mask.hidden = !showHistory && !showDetails; + history.hidden = !showHistory; + details.hidden = !showDetails; + if (showHistory) renderHistorySheet(); + if (showDetails) renderDetailsSheet(); +} + +function closeModal() { + state.modalMode = ""; + state.historyMore = {}; + renderModal(); +} + +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 bindEvents() { + document.querySelector("[data-action='back']").addEventListener("click", closePage); + document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language")); + document.querySelectorAll("[data-route]").forEach((node) => { + node.addEventListener("click", () => navigateTo(node.dataset.route)); + }); + document.querySelector("[data-action='sendGift']").addEventListener("click", () => navigateTo("/bd-item-distribution")); + document.querySelector("[data-action='closeModal']").addEventListener("click", closeModal); +} + +async function initializePageData() { + const tasks = [fetchUserProfile(), fetchTeamInfo(), fetchIdentity()]; + await Promise.allSettled(tasks); +} + +async function init() { + setLanguage(); + setIcons(); + bindEvents(); + renderBalance(); + clearPayee(); + setIdentity("BD_LEADER"); + + try { + await connectToApp(); + await initializePageData(); + await fetchBalanceTotal(); + } catch (error) { + console.warn("BD leader center initialization failed:", error); + showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance")); + } finally { + renderBalance(); + } +} + +init(); diff --git a/h5/bd-leader-center/index.html b/h5/bd-leader-center/index.html index 37291a7..a1b4538 100644 --- a/h5/bd-leader-center/index.html +++ b/h5/bd-leader-center/index.html @@ -13,23 +13,128 @@ - Yumi H5 - - - - - + BD Leader Center + -
+
+
+ +

BD Leader Center

+ +
+ +
+
+
+
+
My available salary
+ +
+
+ + $0 +
+
+
+ + +
+
+ + + + + diff --git a/h5/bd-list/index.html b/h5/bd-list/index.html new file mode 100644 index 0000000..ce5d8d0 --- /dev/null +++ b/h5/bd-list/index.html @@ -0,0 +1,13 @@ + + + + + + BD List + + + + + + + diff --git a/h5/cash-out-details/index.html b/h5/cash-out-details/index.html new file mode 100644 index 0000000..d9a1c68 --- /dev/null +++ b/h5/cash-out-details/index.html @@ -0,0 +1,13 @@ + + + + + + Cash Out Details + + + + + + + diff --git a/h5/cash-out/index.html b/h5/cash-out/index.html new file mode 100644 index 0000000..65fd9d0 --- /dev/null +++ b/h5/cash-out/index.html @@ -0,0 +1,13 @@ + + + + + + Cash Out + + + + + + + diff --git a/h5/cash-withdraw/index.html b/h5/cash-withdraw/index.html new file mode 100644 index 0000000..ea8127d --- /dev/null +++ b/h5/cash-withdraw/index.html @@ -0,0 +1,13 @@ + + + + + + Cash Withdraw + + + + + + + diff --git a/h5/center-static/center-static.css b/h5/center-static/center-static.css new file mode 100644 index 0000000..7657cd1 --- /dev/null +++ b/h5/center-static/center-static.css @@ -0,0 +1,318 @@ +* { + 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%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: transparent; + overflow-x: hidden; +} + +body { + color: #1f3528; + background: #edf8f1; +} + +button { + border: 0; + background: transparent; + color: inherit; + font: inherit; +} + +.center-page { + min-height: 100vh; + overflow-y: auto; + 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; + font-size: 1.35em; + font-weight: 700; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #153d28; +} + +.icon-button, +.language-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + color: #1f6f46; +} + +.icon-button span { + display: block; + font-size: 2em; + line-height: 1; +} + +.language-button { + justify-self: end; + padding: 0 10px; + border-radius: 999px; + background: #dff5e7; + color: #24724a; + font-size: 0.86em; +} + +.content { + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px; +} + +.surface-card { + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #ffffff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.hero-card { + position: relative; + padding: 18px; + background: #f8fff9; + border-color: #94d9ae; + overflow: hidden; +} + +.hero-card::after { + content: ""; + position: absolute; + right: 16px; + bottom: 16px; + width: 64px; + height: 64px; + border-radius: 20px; + background: #d7f5e2; + border: 10px solid #ecfbf1; +} + +.hero-row { + position: relative; + z-index: 1; + display: flex; + justify-content: space-between; + gap: 10px; +} + +.hero-title { + font-weight: 700; + color: #244735; +} + +.hero-link { + flex-shrink: 0; + padding: 4px 10px; + border-radius: 999px; + background: #fff3c8; + color: #846216; + font-weight: 700; +} + +.hero-value { + position: relative; + z-index: 1; + margin-top: 6px; + font-size: 1.65em; + font-weight: 800; + color: #105c36; +} + +.summary-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.summary-item { + padding: 12px; + border-radius: 14px; + border: 1px solid #cce9d7; + background: #fbfffc; +} + +.summary-item:nth-child(2n) { + background: #f7fbec; +} + +.summary-label { + font-size: 0.83em; + font-weight: 700; + color: #587060; +} + +.summary-value { + margin-top: 4px; + font-size: 1.12em; + font-weight: 800; + color: #153d28; +} + +.task-card { + padding: 14px 16px 16px; + background: #fffaf0; + border-color: #e7c777; +} + +.task-title { + margin-bottom: 10px; + font-weight: 800; + color: #3f3826; +} + +.task-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + text-align: center; +} + +.task-label { + margin-bottom: 6px; + font-size: 0.9em; + font-weight: 800; + color: #4b4536; +} + +.task-value { + font-size: 0.95em; + font-weight: 800; + color: #1f3528; +} + +.menu-card { + padding: 10px; + background: #fbfffc; +} + +.section-title { + padding: 4px 4px 10px; + font-size: 1.02em; + font-weight: 800; + color: #1f5134; +} + +.section-title:empty { + display: none; +} + +.menu-row { + width: 100%; + min-height: 4.5em; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-radius: 14px; + text-align: left; +} + +.menu-row:nth-of-type(4n + 2) { + background: #eefbf3; +} + +.menu-row:nth-of-type(4n + 3) { + background: #f7fbec; +} + +.menu-row:nth-of-type(4n + 4) { + background: #eef7fb; +} + +.menu-row:nth-of-type(4n + 5) { + background: #fff5ed; +} + +.menu-label { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 12px; +} + +.menu-icon { + width: 2.65em; + height: 2.65em; + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + border-radius: 14px; + border: 1px solid #bce5ca; + background: #d7f5e2; + color: #1e6b43; + font-size: 1em; + font-weight: 800; +} + +.menu-text { + min-width: 0; + font-size: 1em; + font-weight: 800; + color: #173a28; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.arrow { + flex-shrink: 0; + color: #7fa48d; + font-size: 1.35em; + font-weight: 800; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 20; + 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; +} diff --git a/h5/center-static/center-static.js b/h5/center-static/center-static.js new file mode 100644 index 0000000..f45a5c8 --- /dev/null +++ b/h5/center-static/center-static.js @@ -0,0 +1,297 @@ +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 = `
`; + 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 = `
`; + 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 = `
`; + 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 = ``; + 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(); diff --git a/h5/center-static/static-route.css b/h5/center-static/static-route.css new file mode 100644 index 0000000..b0c8e89 --- /dev/null +++ b/h5/center-static/static-route.css @@ -0,0 +1,177 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +html, +body { + width: 100%; + min-height: 100%; + background: #edf8f1; + color: #1f3528; + -webkit-text-size-adjust: 100%; +} + +button, +input { + font: inherit; +} + +button { + border: 0; + background: transparent; + color: inherit; +} + +.page { + 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.32em; + font-weight: 800; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.back-button, +.lang-button { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + color: #1f6f46; +} + +.back-button span { + font-size: 2em; + line-height: 1; +} + +.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-card, +.list-card, +.empty-card { + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #ffffff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.search-card { + display: flex; + gap: 8px; + padding: 10px; + background: #fbfffc; +} + +.search-card input { + flex: 1; + min-width: 0; + border: 1px solid #cce9d7; + border-radius: 12px; + background: #f8fff9; + padding: 10px 12px; + outline: none; + color: #1f3528; + font-weight: 700; +} + +.search-card button, +.primary-button { + flex-shrink: 0; + border-radius: 12px; + background: #dff5e7; + padding: 0 14px; + color: #1f6f46; + font-weight: 800; +} + +.list-card { + overflow: hidden; +} + +.row { + width: 100%; + min-height: 4.2em; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 12px 14px; + border-bottom: 1px solid #e2f1e7; +} + +.row:last-child { + border-bottom: 0; +} + +.row:nth-child(2n) { + background: #fbfffc; +} + +.row-title { + font-weight: 800; + color: #173a28; +} + +.row-subtitle { + margin-top: 4px; + color: #6d7e72; + font-size: 0.86em; + font-weight: 700; +} + +.tag { + flex-shrink: 0; + border-radius: 999px; + background: #fff3c8; + padding: 5px 9px; + color: #846216; + font-size: 0.82em; + font-weight: 800; +} + +.empty-card { + padding: 24px 16px; + text-align: center; + color: #6d7e72; + font-weight: 700; +} diff --git a/h5/center-static/static-route.js b/h5/center-static/static-route.js new file mode 100644 index 0000000..0094af9 --- /dev/null +++ b/h5/center-static/static-route.js @@ -0,0 +1,56 @@ +const config = window.STATIC_ROUTE || {}; + +function lang() { + const value = new URLSearchParams(window.location.search).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 goBack() { + 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 renderRows(rows) { + const list = document.getElementById("list"); + list.innerHTML = ""; + rows.forEach((row) => { + const node = document.createElement("div"); + node.className = "row"; + node.innerHTML = `
`; + node.querySelector(".row-title").textContent = row.title; + node.querySelector(".row-subtitle").textContent = row.subtitle || ""; + node.querySelector(".tag").textContent = row.tag || "Ready"; + list.appendChild(node); + }); +} + +function init() { + document.title = config.title || "Yumi H5"; + document.getElementById("title").textContent = config.title || "Yumi H5"; + document.getElementById("lang").textContent = lang(); + document.getElementById("keyword").placeholder = config.placeholder || "Search"; + document.getElementById("empty").textContent = config.empty || "No data"; + document.querySelector("[data-action='back']").addEventListener("click", goBack); + document.getElementById("search").addEventListener("click", () => { + const value = document.getElementById("keyword").value.trim(); + const base = config.rows || []; + renderRows(value ? base.filter((row) => JSON.stringify(row).toLowerCase().includes(value.toLowerCase())) : base); + }); + renderRows(config.rows || []); + document.getElementById("list").hidden = !(config.rows || []).length; + document.getElementById("empty").hidden = (config.rows || []).length > 0; +} + +init(); diff --git a/h5/exchange-gold-coins/index.html b/h5/exchange-gold-coins/index.html new file mode 100644 index 0000000..dd21fa7 --- /dev/null +++ b/h5/exchange-gold-coins/index.html @@ -0,0 +1,13 @@ + + + + + + Exchange Coins + + + + + + + diff --git a/h5/history-salary/index.html b/h5/history-salary/index.html new file mode 100644 index 0000000..c2cc2c6 --- /dev/null +++ b/h5/history-salary/index.html @@ -0,0 +1,13 @@ + + + + + + History Salary + + + + + + + diff --git a/h5/host-center/host-center.css b/h5/host-center/host-center.css new file mode 100644 index 0000000..cced638 --- /dev/null +++ b/h5/host-center/host-center.css @@ -0,0 +1,431 @@ +* { + 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-center { + 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; + z-index: 2; + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} + +.section-title { + display: flex; + align-items: center; + margin: 0 1%; + color: #173a28; + font-size: 1.25em; + font-weight: 700; +} + +.surface-card { + margin: 0 1%; + border: 1px solid #b7e0c6; + border-radius: 20px; + background: #fbfffc; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); +} + +.agency-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; +} + +.avatar { + position: relative; + width: 50px; + height: 50px; + flex: 0 0 50px; + overflow: hidden; + border-radius: 25px; + background: #91d9aa; + color: #fff; + font-size: 20px; + font-weight: 700; +} + +.avatar img, +.avatar span { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.avatar img { + display: block; + object-fit: cover; +} + +.avatar span { + display: flex; + align-items: center; + justify-content: center; +} + +.user-info { + flex: 1; + min-width: 0; +} + +.agency-name { + width: 100%; + margin-bottom: 4px; + color: #244735; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.agency-id { + color: #647468; + font-size: 0.9em; +} + +.leave-button { + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 4px 12px; + border: 1px solid #ff9500; + border-radius: 16px; + color: #ff9500; + font-weight: 700; +} + +.salary-card, +.task-card, +.menu-card { + padding: 16px; +} + +.card-row, +.menu-row { + display: flex; + align-items: center; + justify-content: space-between; +} + +.card-title, +.task-title { + color: #244735; + font-size: 1.08em; + font-weight: 700; +} + +.text-link { + color: rgba(0, 0, 0, 0.42); + cursor: pointer; + font-weight: 600; +} + +.salary-value { + margin-top: 8px; + display: flex; + align-items: center; + gap: 6px; + color: #105c36; + font-size: 1.45em; + font-weight: 700; +} + +.money-icon { + width: 1.05em; + height: 1.05em; + display: block; + flex: 0 0 auto; +} + +.task-card { + background: #fffaf0; + border-color: #e7c777; +} + +.task-title { + margin-bottom: 12px; +} + +.task-grid { + display: flex; + justify-content: space-between; +} + +.task-item { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; +} + +.task-label, +.task-value { + font-weight: 700; +} + +.task-label { + margin-bottom: 5px; + color: #4b4536; +} + +.task-value { + color: #1f3528; +} + +.menu-card { + display: flex; + flex-direction: column; + gap: 4vw; +} + +.menu-row { + width: 100%; + min-height: 32px; + text-align: left; +} + +.menu-main { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 1vw; +} + +.menu-main span { + min-width: 0; + color: #173a28; + font-size: 1.4em; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-icon { + display: block; + width: 1.6em; + flex: 0 0 auto; +} + +.arrow-icon { + display: block; + width: 1.5em; + flex: 0 0 auto; +} + +.divider { + border-bottom: 1px solid #d9eadf; +} + +.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; +} + +.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: 2vw; + 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.8); + font-weight: 700; + text-align: center; +} + +.modal-submessage { + color: rgba(0, 0, 0, 0.4); + font-size: 0.8em; + font-weight: 700; + text-align: center; +} + +.modal-actions { + width: 80%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.modal-primary, +.modal-secondary { + display: flex; + align-items: center; + justify-content: center; + min-height: 38px; + padding: 8px 12px; + border-radius: 70px; + font-weight: 800; +} + +.modal-primary { + width: 50%; + background: #ff9500; + color: #fff; +} + +.modal-actions .modal-primary, +.modal-secondary { + width: 45%; +} + +.modal-secondary { + border: 1px solid #ffb805; + background: #fff; + color: #ff9500; +} + +[hidden] { + display: none !important; +} + +[dir="rtl"] .menu-row, +[dir="rtl"] .page-content { + text-align: right; +} + +@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-center/host-center.js b/h5/host-center/host-center.js new file mode 100644 index 0000000..8445100 --- /dev/null +++ b/h5/host-center/host-center.js @@ -0,0 +1,653 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; +const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png"; +const icons = { + policy: "./icons/policy.svg", + exchange: "./icons/exchange.svg", + transfer: "./icons/transfer.svg", + withdraw: "./icons/withdraw.svg", + bank: "./icons/bank.svg", + money: "./icons/money.svg", + 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_center: "Host Center", + my_agency: "My agency", + user_id_prefix: "ID:", + leave: "Leave", + my_available_salary: "My available salary", + my_salary_details: "My salary details", + todays_task: "Today's task", + anchor_type: "Anchor type", + minutes: "Minutes", + gift_task: "Gift Task", + platform_policy: "Platform policy", + exchange_coins: "Exchange coins", + transfer: "Transfer", + cash_withdraw: "Cash withdraw", + bank_accounts: "Bank accounts", + prompt: "Prompt", + tips: "Tips", + fill_info_tips: "Please complete your information first.", + card_issue_tips: "Please make sure your bank card and KYC information are available.", + to_improve: "To improve", + leave_agent_tips: "Are you sure you want to leave?", + cancel: "Cancel", + confirm: "Confirm", + failed_to_load_balance: "Failed to load balance. Please try again.", + something_went_wrong: "Something went wrong." + }, + ar: { + host_center: "مركز المضيف", + my_agency: "وكالتي", + user_id_prefix: "ID:", + leave: "مغادرة", + my_available_salary: "راتبي المتاح", + my_salary_details: "تفاصيل الراتب", + todays_task: "مهمة اليوم", + anchor_type: "نوع المضيف", + minutes: "الدقائق", + gift_task: "مهمة الهدايا", + platform_policy: "سياسة المنصة", + exchange_coins: "استبدال العملات", + transfer: "تحويل", + cash_withdraw: "سحب نقدي", + bank_accounts: "الحسابات البنكية", + prompt: "تنبيه", + tips: "نصائح", + fill_info_tips: "يرجى إكمال معلوماتك أولا.", + card_issue_tips: "يرجى التأكد من توفر البطاقة البنكية ومعلومات KYC.", + to_improve: "إكمال", + leave_agent_tips: "هل أنت متأكد أنك تريد المغادرة؟", + cancel: "إلغاء", + confirm: "تأكيد", + failed_to_load_balance: "فشل تحميل الرصيد. يرجى المحاولة مرة أخرى.", + something_went_wrong: "حدث خطأ ما." + }, + tr: { + host_center: "Host Merkezi", + my_agency: "Ajansım", + user_id_prefix: "ID:", + leave: "Ayrıl", + my_available_salary: "Kullanılabilir maaşım", + my_salary_details: "Maaş detayları", + todays_task: "Bugünkü görev", + anchor_type: "Host türü", + minutes: "Dakika", + gift_task: "Hediye görevi", + platform_policy: "Platform politikası", + exchange_coins: "Coin değiştir", + transfer: "Transfer", + cash_withdraw: "Para çek", + bank_accounts: "Banka hesapları", + prompt: "Uyarı", + tips: "İpuçları", + fill_info_tips: "Lütfen önce bilgilerinizi tamamlayın.", + card_issue_tips: "Lütfen banka kartı ve KYC bilgilerinizin mevcut olduğundan emin olun.", + to_improve: "Tamamla", + leave_agent_tips: "Ayrılmak istediğinizden emin misiniz?", + cancel: "İptal", + confirm: "Onayla", + failed_to_load_balance: "Bakiye yüklenemedi. Lütfen tekrar deneyin.", + something_went_wrong: "Bir şeyler ters gitti." + }, + bn: { + host_center: "হোস্ট সেন্টার", + my_agency: "আমার এজেন্সি", + user_id_prefix: "ID:", + leave: "ছাড়ুন", + my_available_salary: "আমার উপলভ্য বেতন", + my_salary_details: "বেতনের বিবরণ", + todays_task: "আজকের কাজ", + anchor_type: "হোস্ট ধরন", + minutes: "মিনিট", + gift_task: "গিফট টাস্ক", + platform_policy: "প্ল্যাটফর্ম নীতি", + exchange_coins: "কয়েন এক্সচেঞ্জ", + transfer: "ট্রান্সফার", + cash_withdraw: "ক্যাশ উত্তোলন", + bank_accounts: "ব্যাংক অ্যাকাউন্ট", + prompt: "প্রম্পট", + tips: "টিপস", + fill_info_tips: "প্রথমে আপনার তথ্য সম্পূর্ণ করুন।", + card_issue_tips: "আপনার ব্যাংক কার্ড এবং KYC তথ্য আছে কিনা নিশ্চিত করুন।", + to_improve: "সম্পূর্ণ করুন", + leave_agent_tips: "আপনি কি নিশ্চিত যে আপনি ছাড়তে চান?", + cancel: "বাতিল", + confirm: "নিশ্চিত", + failed_to_load_balance: "ব্যালেন্স লোড করতে ব্যর্থ। আবার চেষ্টা করুন।", + something_went_wrong: "কিছু ভুল হয়েছে।" + } +}; + +const state = { + lang: resolveLanguage(), + profile: {}, + teamInfo: null, + identityInfo: null, + agency: {}, + salaryInfo: { currentSalary: "0" }, + taskInfo: { anchorType: "Chat", minutesProgress: 0, minutesTotal: 120, giftTask: 0 }, + bankCards: [], + activeBankCard: null, + withdrawInfo: null, + withdrawEnabled: false +}; + +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)) { + 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; + } + + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") { + try { + const raw = window.app.getAccessOrigin(); + if (raw) setHeadersFromApp(parseAccessOrigin(raw)); + } catch (error) { + console.error("Failed to parse access origin:", error); + } + 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 + }); + 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 || `Request failed: ${response.status}`); + error.response = data; + throw error; + } + return data; +} + +function formatMoney(value) { + if (value === null || value === undefined || value === "") return "0"; + const number = Number(value); + if (Number.isNaN(number)) return String(value); + return number.toFixed(2); +} + +function getMemberId() { + return state.profile.id || state.profile.userId || state.profile.account || null; +} + +function getTeamId() { + if (state.teamInfo && state.teamInfo.id) return state.teamInfo.id; + const cached = localStorage.getItem("teamInfo"); + if (cached) { + try { + const data = JSON.parse(cached); + if (data && data.id) return data.id; + } catch (error) { + console.error("Failed to parse teamInfo:", error); + } + } + return state.agency.teamId || state.agency.id || null; +} + +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); + } + } + const result = await apiGet("/team/member/profile"); + if (result.status && result.body) { + state.profile = result.body; + localStorage.setItem("userProfile", JSON.stringify(result.body)); + } +} + +async function fetchAgencyProfile() { + const result = await apiGet("/team/team-own/profile"); + if (result.status && result.body) state.agency = result.body; +} + +async function fetchTeamEntry() { + const cached = localStorage.getItem("teamInfo"); + if (cached) { + try { + state.teamInfo = JSON.parse(cached); + } catch (error) { + console.error("Failed to parse teamInfo:", error); + } + } + const result = await apiGet("/team/entry"); + if (result.status && result.body) { + state.teamInfo = result.body.teamProfile || result.body; + localStorage.setItem("teamInfo", JSON.stringify(state.teamInfo)); + } +} + +async function fetchBankBalance() { + const result = await apiGet("/wallet/bank/balance"); + if (result.status && typeof result.body === "number") { + state.salaryInfo.currentSalary = result.body.toFixed(2); + } else if (result.status && result.body) { + state.salaryInfo.currentSalary = formatMoney(result.body.currentSalary ?? result.body.availableBalance ?? result.body.balance ?? 0); + } else { + state.salaryInfo.currentSalary = "0"; + } +} + +async function fetchWorkStatistics() { + const memberId = getMemberId(); + if (!memberId) { + console.warn("No user ID available"); + return; + } + const result = await apiGet(`/team/member/work/statistics-now?dataType=DAILY&memberId=${encodeURIComponent(memberId)}`); + if (result.status && result.body) { + const ownTime = Number(result.body.ownTime) || 0; + const otherTime = Number(result.body.otherTime) || 0; + const total = ownTime + otherTime; + state.taskInfo.anchorType = "Chat"; + state.taskInfo.minutesProgress = total >= state.taskInfo.minutesTotal ? state.taskInfo.minutesTotal : total; + state.taskInfo.giftTask = result.body.acceptGiftValue || 0; + } +} + +async function fetchMemberWork() { + const memberId = getMemberId(); + if (!memberId) return; + const result = await apiGet(`/team/member-work?userId=${encodeURIComponent(memberId)}`); + if (result.status && result.body) console.debug("member work data:", result.body); +} + +async function fetchBankCards() { + const result = await apiGet("/wallet/bank-card/list"); + if (result.status && result.body) { + state.bankCards = result.body; + state.activeBankCard = state.bankCards.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 }; + } +} + +async function fetchWithdrawEnabled() { + const result = await apiGet("/wallet/bank/host-salary/withdraw/enabled"); + if (result.status) state.withdrawEnabled = Boolean(result.body); +} + +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; + state.identityInfo = result.body; + const roles = calculateRoles(result.body); + const primaryRole = getPrimaryRole(roles); + if (!roles.includes("anchor")) { + const defaultPage = getDefaultPage(primaryRole); + if (defaultPage !== "/host-center") navigateTo(defaultPage); + } +} + +async function leaveAgency() { + await apiPost("/team/user/apply", { + reason: "QUIT", + teamId: getTeamId() + }); +} + +async function runTask(task, label) { + try { + await task(); + } catch (error) { + console.error(`${label} failed:`, error); + if (label === "bank balance") showToast(t("failed_to_load_balance")); + if (label === "withdraw info" || label === "withdraw enabled") showToast(error.response?.errorMsg || error.message || t("something_went_wrong")); + } +} + +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.app && typeof window.app.closePage === "function") { + window.app.closePage(); + return; + } + if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { + window.FlutterPageControl.postMessage("close_page"); + return; + } + if (window.history.length > 1) { + window.history.back(); + } +} + +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 showModal(id) { + const modal = $(`#${id}`); + if (modal) modal.hidden = false; +} + +function hideModal(id) { + const modal = $(`#${id}`); + if (modal) modal.hidden = true; +} + +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() { + $("#policyIcon").src = icons.policy; + $("#exchangeIcon").src = icons.exchange; + $("#transferIcon").src = icons.transfer; + $("#withdrawIcon").src = icons.withdraw; + $("#bankIcon").src = icons.bank; + $("#moneyIcon").src = icons.money; + $("#backIcon").src = icons.back; + ["#arrowIcon1", "#arrowIcon2", "#arrowIcon3", "#arrowIcon4", "#arrowIcon5"].forEach((selector) => { + $(selector).src = icons.arrow; + }); +} + +function renderAgency() { + const avatar = $("#agencyAvatar"); + const fallback = $("#avatarFallback"); + const displayName = state.agency.userNickname || state.agency.name || "-"; + const account = state.agency.ownSpecialId?.account || state.agency.account || "-"; + + $("#agencyName").textContent = displayName; + $("#agencyId").textContent = account; + fallback.textContent = (displayName || "U").slice(0, 1).toUpperCase(); + avatar.alt = displayName; + avatar.src = state.agency.userAvatar || ""; + avatar.hidden = !state.agency.userAvatar; +} + +function renderData() { + $("#currentSalary").textContent = state.salaryInfo.currentSalary; + $("#anchorType").textContent = state.taskInfo.anchorType; + $("#minutesProgress").textContent = state.taskInfo.minutesProgress; + $("#minutesTotal").textContent = state.taskInfo.minutesTotal; + $("#giftTask").textContent = state.taskInfo.giftTask; + $("#cashWithdrawRow").hidden = !state.withdrawEnabled; + $("#cashDivider").hidden = !state.withdrawEnabled; +} + +function render() { + renderI18n(); + renderIcons(); + renderAgency(); + renderData(); +} + +function bindEvents() { + $("[data-action='back']").addEventListener("click", closePage); + $("[data-action='language']").addEventListener("click", () => navigateTo("/language")); + $("[data-action='leave']").addEventListener("click", () => showModal("leaveModal")); + + $$("[data-route]").forEach((node) => { + node.addEventListener("click", () => navigateTo(node.dataset.route)); + }); + + $("[data-action='cash-withdraw']").addEventListener("click", () => { + if (!state.withdrawInfo || !state.activeBankCard) { + showModal("infoMissingModal"); + return; + } + navigateTo("/cash-withdraw"); + }); + + $("#agencyAvatar").addEventListener("error", (event) => { + event.target.onerror = null; + event.target.src = DEFAULT_AVATAR; + event.target.hidden = false; + }); + + $("#infoMissingModal").addEventListener("click", () => hideModal("infoMissingModal")); + $("#leaveModal").addEventListener("click", () => hideModal("leaveModal")); + $$(".modal-card").forEach((node) => node.addEventListener("click", (event) => event.stopPropagation())); + $("[data-action='improve']").addEventListener("click", () => navigateTo("/host-setting")); + $("[data-action='cancel-leave']").addEventListener("click", () => hideModal("leaveModal")); + $("[data-action='confirm-leave']").addEventListener("click", async () => { + try { + await leaveAgency(); + } catch (error) { + showToast(error.response?.errorMsg || error.errorMsg || t("something_went_wrong")); + } finally { + hideModal("leaveModal"); + } + }); +} + +async function init() { + localStorage.setItem("identity", JSON.stringify({ identity: "HOST" })); + localStorage.removeItem("payee"); + bindEvents(); + render(); + + try { + 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") + ]); + } catch (error) { + console.error("Host center data request failed:", error); + showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance")); + } finally { + render(); + } +} + +init(); diff --git a/h5/host-center/icons/arrow.svg b/h5/host-center/icons/arrow.svg new file mode 100644 index 0000000..7e4862b --- /dev/null +++ b/h5/host-center/icons/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/host-center/icons/back.svg b/h5/host-center/icons/back.svg new file mode 100644 index 0000000..b74cc76 --- /dev/null +++ b/h5/host-center/icons/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/h5/host-center/icons/bank.svg b/h5/host-center/icons/bank.svg new file mode 100644 index 0000000..c334b2b --- /dev/null +++ b/h5/host-center/icons/bank.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/h5/host-center/icons/exchange.svg b/h5/host-center/icons/exchange.svg new file mode 100644 index 0000000..023017c --- /dev/null +++ b/h5/host-center/icons/exchange.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/h5/host-center/icons/money.svg b/h5/host-center/icons/money.svg new file mode 100644 index 0000000..29b9c80 --- /dev/null +++ b/h5/host-center/icons/money.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/h5/host-center/icons/policy.svg b/h5/host-center/icons/policy.svg new file mode 100644 index 0000000..91fb8fa --- /dev/null +++ b/h5/host-center/icons/policy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/h5/host-center/icons/transfer.svg b/h5/host-center/icons/transfer.svg new file mode 100644 index 0000000..b6b3815 --- /dev/null +++ b/h5/host-center/icons/transfer.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/h5/host-center/icons/withdraw.svg b/h5/host-center/icons/withdraw.svg new file mode 100644 index 0000000..c03c007 --- /dev/null +++ b/h5/host-center/icons/withdraw.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/h5/host-center/index.html b/h5/host-center/index.html index 1123132..bc5a8ca 100644 --- a/h5/host-center/index.html +++ b/h5/host-center/index.html @@ -1,35 +1,111 @@ - + - - - - - - - - - - Yumi H5 - - - - - + + + Host Center + -
+
+ + +
+
My agency
+ +
+
+ + U +
+ + +
+ +
+
+
My available salary
+ +
+
0
+
+ +
+
Today's task
+
+
+
Anchor type
+
Chat
+
+
+
Minutes
+
0/120
+
+
+
Gift Task
+
0
+
+
+
+ + +
+ + + + + + +
+ + diff --git a/h5/host-setting/index.html b/h5/host-setting/index.html new file mode 100644 index 0000000..d1ec1ff --- /dev/null +++ b/h5/host-setting/index.html @@ -0,0 +1,13 @@ + + + + + + Host Setting + + + + + + + diff --git a/h5/income-details/index.html b/h5/income-details/index.html new file mode 100644 index 0000000..b889420 --- /dev/null +++ b/h5/income-details/index.html @@ -0,0 +1,13 @@ + + + + + + Income Details + + + + + + + diff --git a/h5/information-details/index.html b/h5/information-details/index.html new file mode 100644 index 0000000..3a1e745 --- /dev/null +++ b/h5/information-details/index.html @@ -0,0 +1,13 @@ + + + + + + Information Details + + + + + + + diff --git a/h5/invite-agency/index.html b/h5/invite-agency/index.html new file mode 100644 index 0000000..04b88bc --- /dev/null +++ b/h5/invite-agency/index.html @@ -0,0 +1,110 @@ + + + + + + Invite User To Become Agent + + + +
+
+
+
+ +
+

Invite User To Become Agent

+
+
+
English
+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+ +
+
please input user id
+
+
+ + + + + + diff --git a/h5/invite-agency/yumi-invite-agency.css b/h5/invite-agency/yumi-invite-agency.css new file mode 100644 index 0000000..828a8df --- /dev/null +++ b/h5/invite-agency/yumi-invite-agency.css @@ -0,0 +1,177 @@ +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 new file mode 100644 index 0000000..744df40 --- /dev/null +++ b/h5/invite-agency/yumi-invite-agency.js @@ -0,0 +1,694 @@ +(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/index.html b/h5/invite-bd-leader/index.html new file mode 100644 index 0000000..28827b8 --- /dev/null +++ b/h5/invite-bd-leader/index.html @@ -0,0 +1,109 @@ + + + + + + Invite User To Become BD Leader + + + +
+
+
+
+ +
+

Invite User To Become BD Leader

+
+
+
English
+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+ +
+
please input user id
+
+
+ + + + + + diff --git a/h5/invite-bd-leader/yumi-invite-bd-leader.css b/h5/invite-bd-leader/yumi-invite-bd-leader.css new file mode 100644 index 0000000..828a8df --- /dev/null +++ b/h5/invite-bd-leader/yumi-invite-bd-leader.css @@ -0,0 +1,177 @@ +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-bd-leader/yumi-invite-bd-leader.js b/h5/invite-bd-leader/yumi-invite-bd-leader.js new file mode 100644 index 0000000..e889b1c --- /dev/null +++ b/h5/invite-bd-leader/yumi-invite-bd-leader.js @@ -0,0 +1,699 @@ +(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 ENDPOINTS = { + invitedList: "/team/bd/invite-bdleader-message-own", + invite: "/team/bd/invite-bdleader", + cancelPrefix: "/team/bd/invite-bdleader-cancel/", + }; + + const TEXT = { + pageTitle: "Invite User To Become BD Leader", + 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", ENDPOINTS.invitedList); + 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", ENDPOINTS.invite, { + 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", ENDPOINTS.cancelPrefix + 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/index.html b/h5/invite-bd/index.html new file mode 100644 index 0000000..4b32ca9 --- /dev/null +++ b/h5/invite-bd/index.html @@ -0,0 +1,109 @@ + + + + + + Invite User To Become BD + + + +
+
+
+
+ +
+

Invite User To Become BD

+
+
+
English
+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+ +
+
please input user id
+
+
+ + + + + + diff --git a/h5/invite-bd/yumi-invite-bd.css b/h5/invite-bd/yumi-invite-bd.css new file mode 100644 index 0000000..828a8df --- /dev/null +++ b/h5/invite-bd/yumi-invite-bd.css @@ -0,0 +1,177 @@ +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-bd/yumi-invite-bd.js b/h5/invite-bd/yumi-invite-bd.js new file mode 100644 index 0000000..bec1e7f --- /dev/null +++ b/h5/invite-bd/yumi-invite-bd.js @@ -0,0 +1,699 @@ +(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 ENDPOINTS = { + invitedList: "/team/bd/invite-bd-message-own", + invite: "/team/bd/invite-bd", + cancelPrefix: "/team/bd/invite-bd-cancel/", + }; + + const TEXT = { + pageTitle: "Invite User To Become BD", + 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", ENDPOINTS.invitedList); + 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", ENDPOINTS.invite, { + 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", ENDPOINTS.cancelPrefix + 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-members/index.html b/h5/invite-members/index.html new file mode 100644 index 0000000..5f631a5 --- /dev/null +++ b/h5/invite-members/index.html @@ -0,0 +1,13 @@ + + + + + + Invite Members + + + + + + + diff --git a/h5/invite-recharge-agency/index.html b/h5/invite-recharge-agency/index.html new file mode 100644 index 0000000..4e0c87a --- /dev/null +++ b/h5/invite-recharge-agency/index.html @@ -0,0 +1,109 @@ + + + + + + Invite User To Become Recharge Agent + + + +
+
+
+
+ +
+

Invite User To Become Recharge Agent

+
+
+
English
+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+ +
+
please input user id
+
+
+ + + + + + diff --git a/h5/invite-recharge-agency/yumi-invite-recharge-agency.css b/h5/invite-recharge-agency/yumi-invite-recharge-agency.css new file mode 100644 index 0000000..828a8df --- /dev/null +++ b/h5/invite-recharge-agency/yumi-invite-recharge-agency.css @@ -0,0 +1,177 @@ +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-recharge-agency/yumi-invite-recharge-agency.js b/h5/invite-recharge-agency/yumi-invite-recharge-agency.js new file mode 100644 index 0000000..f0092c1 --- /dev/null +++ b/h5/invite-recharge-agency/yumi-invite-recharge-agency.js @@ -0,0 +1,699 @@ +(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 ENDPOINTS = { + invitedList: "/team/bd/invite-recharge-agent-message-own", + invite: "/team/bd/invite-recharge-agent", + cancelPrefix: "/team/bd/invite-recharge-agent-cancel/", + }; + + const TEXT = { + pageTitle: "Invite User To Become Recharge 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", ENDPOINTS.invitedList); + 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", ENDPOINTS.invite, { + 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", ENDPOINTS.cancelPrefix + 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/item-distribution/index.html b/h5/item-distribution/index.html new file mode 100644 index 0000000..911650a --- /dev/null +++ b/h5/item-distribution/index.html @@ -0,0 +1,13 @@ + + + + + + Item Distribution + + + + + + + diff --git a/h5/language/index.html b/h5/language/index.html new file mode 100644 index 0000000..9bced21 --- /dev/null +++ b/h5/language/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + Yumi H5 + + + + + + + +
+ + diff --git a/h5/logic-pages/app.css b/h5/logic-pages/app.css new file mode 100644 index 0000000..fcb8a52 --- /dev/null +++ b/h5/logic-pages/app.css @@ -0,0 +1,355 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +html, +body { + width: 100%; + min-height: 100%; + background: #edf8f1; + color: #1f3528; + -webkit-text-size-adjust: 100%; +} + +button, +input { + font: inherit; +} + +button { + border: 0; + background: transparent; + color: inherit; +} + +.page { + min-height: 100vh; + background: #edf8f1; +} + +.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: #f8fff9; + border-bottom: 1px solid #cce9d7; + box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14); +} + +.header h1 { + min-width: 0; + color: #153d28; + font-size: 1.28em; + font-weight: 800; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.back, +.lang { + min-width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + color: #1f6f46; +} + +.back span { + font-size: 2em; + line-height: 1; +} + +.lang { + 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; +} + +.card { + border: 1px solid #b7e0c6; + border-radius: 18px; + background: #fff; + box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12); + overflow: hidden; +} + +.card-pad { + padding: 14px; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.metric { + padding: 12px; + border-radius: 14px; + border: 1px solid #cce9d7; + background: #fbfffc; +} + +.metric:nth-child(2n) { + background: #f7fbec; +} + +.label { + font-size: 0.84em; + color: #5f7468; + font-weight: 800; +} + +.value { + margin-top: 4px; + color: #153d28; + font-size: 1.12em; + font-weight: 900; +} + +.section-title { + color: #1f5134; + font-size: 1.04em; + font-weight: 900; +} + +.row { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 13px 14px; + border-bottom: 1px solid #e2f1e7; +} + +.row:last-child { + border-bottom: 0; +} + +.avatar { + width: 46px; + height: 46px; + flex-shrink: 0; + border-radius: 50%; + background: #dff5e7; + object-fit: cover; +} + +.main { + flex: 1; + min-width: 0; +} + +.title { + color: #173a28; + font-weight: 900; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.sub { + margin-top: 4px; + color: #6d7e72; + font-size: 0.84em; + font-weight: 700; +} + +.side { + flex-shrink: 0; + text-align: right; + font-size: 0.84em; + font-weight: 800; + color: #375744; +} + +.tag { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: #fff3c8; + padding: 5px 9px; + color: #846216; + font-size: 0.82em; + font-weight: 900; +} + +.toolbar, +.tabs, +.form-row { + display: flex; + align-items: center; + gap: 8px; +} + +.toolbar input, +.form-row input, +.input { + width: 100%; + border: 1px solid #cce9d7; + border-radius: 12px; + background: #f8fff9; + padding: 10px 12px; + outline: none; + color: #1f3528; + font-weight: 800; +} + +.button, +.primary, +.ghost { + min-height: 38px; + border-radius: 12px; + padding: 8px 12px; + font-weight: 900; +} + +.primary { + background: #dff5e7; + border: 1px solid #b7e0c6; + color: #1f6f46; +} + +.ghost { + border: 1px solid #cce9d7; + color: #375744; + background: #fff; +} + +.tabs button { + flex: 1; + border: 1px solid #cce9d7; + background: #fff; + color: #375744; + border-radius: 999px; + padding: 9px 8px; + font-weight: 900; +} + +.tabs button.active { + background: #dff5e7; + color: #1f6f46; + border-color: #94d9ae; +} + +.grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.grid.three { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.tile { + min-height: 96px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + border: 1px solid #cce9d7; + border-radius: 14px; + background: #fbfffc; + color: #173a28; + font-weight: 900; + text-align: center; +} + +.tile.selected { + background: #dff5e7; + border-color: #74c993; +} + +.tile img { + width: 54%; + max-height: 64px; + object-fit: contain; +} + +.empty, +.loading { + padding: 28px 16px; + text-align: center; + color: #6d7e72; + font-weight: 800; +} + +.modal-mask { + position: fixed; + inset: 0; + z-index: 50; + display: none; + align-items: flex-end; + background: rgba(0, 0, 0, 0.45); +} + +.modal-mask.show { + display: flex; +} + +.modal { + width: 100%; + max-height: 82vh; + overflow: auto; + border-radius: 18px 18px 0 0; + background: #fff; + padding: 16px; +} + +.modal.center { + width: calc(100% - 32px); + margin: auto; + border-radius: 18px; +} + +.modal-title { + margin-bottom: 12px; + color: #153d28; + font-weight: 900; + text-align: center; +} + +.toast { + position: fixed; + left: 50%; + bottom: calc(22px + env(safe-area-inset-bottom)); + z-index: 80; + 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: 800; + text-align: center; +} + +[hidden] { + display: none !important; +} diff --git a/h5/logic-pages/app.js b/h5/logic-pages/app.js new file mode 100644 index 0000000..fab9e98 --- /dev/null +++ b/h5/logic-pages/app.js @@ -0,0 +1,662 @@ +const API_BASE_URL = "https://jvapi.haiyihy.com"; + +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" +}; + +const cfg = window.PAGE_CONFIG || {}; +const $ = (selector, root = document) => root.querySelector(selector); +const money = (v) => { + const n = Number(v || 0); + return Number.isFinite(n) ? n.toFixed(2).replace(/\.00$/, "") : String(v || 0); +}; + +function lang() { + const value = new URLSearchParams(location.search).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 splitPairs(value) { + const result = {}; + String(value || "").split(";").forEach((item) => { + const index = item.indexOf("="); + if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); + }); + return result; +} + +function setAppHeaders(raw) { + if (!raw) return; + const data = typeof raw === "string" ? JSON.parse(raw) : raw; + const appIntel = splitPairs(data["Req-App-Intel"]); + const sysOrigin = splitPairs(data["Req-Sys-Origin"]); + headers = { + ...headers, + authorization: data.Authorization || headers.authorization, + "req-lang": data["Req-Lang"] || headers["req-lang"], + "req-app-intel": [`version=${appIntel.version || "1.0.0"}`, `build=${appIntel.build || "1"}`, `channel=${appIntel.channel || "Web"}`].join(";"), + "req-imei": appIntel["Req-Imei"] || headers["req-imei"], + "req-sys-origin": [`origin=${sysOrigin.origin || "LIKEI"}`, `child=${sysOrigin.child || "LIKEI"}`].join(";") + }; +} + +async function connectApp() { + if (!(window.app || window.webkit || window.FlutterPageControl)) { + headers["req-lang"] = lang().toLowerCase(); + return; + } + const raw = await new Promise((resolve) => { + window.renderData = resolve; + window.getIosAccessOriginParam = resolve; + let attempt = 0; + const poll = () => { + attempt += 1; + if (window.app && typeof window.app.getAccessOrigin === "function") return resolve(window.app.getAccessOrigin()); + if (attempt < 50) return setTimeout(poll, 100); + if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") window.FlutterApp.postMessage("requestAccessOrigin"); + resolve(null); + }; + poll(); + }); + if (raw) setAppHeaders(raw); +} + +async function request(method, path, body) { + const response = await fetch(`${API_BASE_URL}${path}`, { + method, + headers: { ...headers, "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 get = (path) => request("GET", path); +const post = (path, body) => request("POST", path, body); + +function getTeamId() { + const keys = ["teamId", "TEAM_ID", "team_id"]; + for (const key of keys) { + const value = localStorage.getItem(key); + if (value) return value.replace(/^"|"$/g, ""); + } + for (const key of ["teamInfo", "userInfo", "identity"]) { + try { + const item = JSON.parse(localStorage.getItem(key) || "{}"); + if (item.teamId || item.id) return item.teamId || item.id; + } catch {} + } + return ""; +} + +function getIdentity() { + try { + const value = JSON.parse(localStorage.getItem("identity") || "{}"); + return value.identity || value.role || "BD"; + } catch { + return "BD"; + } +} + +function toast(message) { + const node = $("#toast"); + node.textContent = message || "Request failed"; + node.hidden = false; + clearTimeout(toast.timer); + toast.timer = setTimeout(() => (node.hidden = true), 2500); +} + +function setLoading(text = "Loading...") { + $("#app").innerHTML = `
${text}
`; +} + +function row({ avatar, title, sub, side, tag, onClick }) { + const node = document.createElement("button"); + node.type = "button"; + node.className = "row"; + node.innerHTML = `${avatar !== undefined ? `` : ""}
${tag ? `` : ""}`; + $(".title", node).textContent = title || "-"; + $(".sub", node).textContent = sub || ""; + $(".side", node).textContent = side || ""; + if (tag) $(".tag", node).textContent = tag; + if (onClick) node.addEventListener("click", onClick); + return node; +} + +function metrics(items) { + return `
${items.map((item) => `
${item.label}
${item.value}
`).join("")}
`; +} + +function showModal(title, content, center = false) { + const mask = $("#modalMask"); + mask.innerHTML = ``; + $(".modal-title", mask).textContent = title || ""; + $(".modal-body", mask).append(content); + mask.classList.add("show"); +} + +function closeModal() { + $("#modalMask").classList.remove("show"); +} + +function navigate(route) { + const url = new URL(route, location.origin); + const langParam = new URLSearchParams(location.search).get("lang"); + if (langParam && !url.searchParams.has("lang")) url.searchParams.set("lang", langParam); + location.href = `${url.pathname}${url.search}${url.hash}`; +} + +function renderShell() { + document.title = cfg.title || "Yumi H5"; + document.body.innerHTML = ` +
+
+ +

${cfg.title || "Yumi H5"}

+ +
+
+ + +
`; + $("[data-back]").addEventListener("click", () => history.length > 1 ? history.back() : window.app?.closePage?.()); + $("#modalMask").addEventListener("click", (event) => { + if (event.target.id === "modalMask") closeModal(); + }); +} + +async function renderTeamHistory() { + setLoading(); + const [listRes, historyRes] = await Promise.allSettled([get(cfg.listEndpoint), get(cfg.historyEndpoint)]); + const bill = listRes.value?.body || {}; + const history = historyRes.value?.body || {}; + $("#app").innerHTML = ` + ${metrics([ + { label: cfg.countLabel, value: bill.memberCount || 0 }, + { label: cfg.totalLabel, value: `$${money(bill.totalSalary || bill.teamSalary || bill.totalRecharge || 0)}` } + ])} +
+
+
${bill.billTitle || cfg.title}
${cfg.totalLabel}: $${money(bill.totalSalary || 0)}
+ +
+
+
`; + const list = $("#list"); + (bill.memberBillList || []).forEach((item) => { + list.appendChild(row({ + avatar: item.agentAvatar || item.userAvatar, + title: item.agentName || item.userName, + sub: `ID: ${item.agentAccount || item.account || "-"}`, + side: `${cfg.memberSide1}: $${money(item.teamSalaryAmount || item.salary || 0)}\n${cfg.memberSide2}: ${item.teamMemberCount || item.hostCount || item.agencyCount || 0}` + })); + }); + if (!list.children.length) list.innerHTML = `
No data
`; + $("#historyBtn").addEventListener("click", () => { + const body = document.createElement("div"); + body.innerHTML = metrics([ + { label: "Total income", value: `$${money(history.totalIncome || 0)}` }, + { label: "Previous period", value: `$${money(history.previousPeriodIncome || 0)}` } + ]); + const wrap = document.createElement("div"); + wrap.className = "card"; + (history.incomeDetails || []).forEach((item) => { + wrap.appendChild(row({ + title: item.billTitle || item.settlementPeriod, + sub: `${cfg.countLabel}: ${item.agencyNumber || item.bdNumber || item.memberCount || 0}`, + side: `$${money(item.teamSalaryAmount || item.teamTotalIncome || item.settlementSalary || 0)}`, + tag: item.statusText || item.status, + onClick: async () => { + if (!cfg.moreEndpoint || !item.billBelong) return; + try { + const more = await get(`${cfg.moreEndpoint}${item.billBelong}`); + const detail = document.createElement("div"); + const card = document.createElement("div"); + card.className = "card"; + (more.body?.memberDetails || more.body?.members || []).forEach((member) => { + card.appendChild(row({ + avatar: member.userAvatar || member.bdUserAvatar || member.avatar, + title: member.userName || member.bdUserName || member.account, + sub: `ID: ${member.account || "-"}`, + side: `$${money(member.salary || member.income || 0)}` + })); + }); + detail.appendChild(card); + showModal(more.body?.period || item.billTitle, detail); + } catch (error) { + toast(error.message); + } + } + })); + }); + if (!wrap.children.length) wrap.innerHTML = `
No data
`; + body.appendChild(wrap); + showModal("History", body); + }); +} + +async function renderAdminTeam() { + setLoading(); + const res = await post("/bd/team/overview", { type: cfg.teamType }); + const data = res.body || {}; + $("#app").innerHTML = ` + ${metrics([ + { label: "Total income", value: `$${money(data.totalIncome || 0)}` }, + { label: "Previous period", value: `$${money(data.previousPeriodIncome || 0)}` }, + { label: cfg.memberTitle, value: data.memberCount || 0 }, + { label: "Team total income", value: `$${money(data.teamTotalIncome || 0)}` } + ])} +
`; + const members = $("#members"); + (data.members || []).forEach((item) => { + members.appendChild(row({ + avatar: item.avatar, + title: item.userName || "-", + sub: `ID: ${item.account || "-"}`, + side: `${cfg.subMemberLabel}: ${item.subMemberCount || 0}\n$${money(item.totalIncome || 0)}`, + tag: "More", + onClick: () => showAdminTeamDetail(item) + })); + }); + if (!members.children.length) members.innerHTML = `
No data
`; +} + +async function showAdminTeamDetail(item) { + try { + const res = await post("/bd/team/member/detail", { targetId: item.userId, type: cfg.detailType || cfg.teamType }); + const data = res.body || {}; + const body = document.createElement("div"); + body.innerHTML = metrics([ + { label: "Total income", value: `$${money(data.totalIncome || 0)}` }, + { label: "Previous period", value: `$${money(data.previousPeriodIncome || 0)}` } + ]); + const card = document.createElement("div"); + card.className = "card"; + (data.incomeDetails || []).forEach((detail) => { + card.appendChild(row({ + title: detail.settlementPeriod, + sub: `${cfg.subMemberLabel}: ${detail.memberCount || 0}`, + side: `$${money(detail.teamTotalIncome || 0)}`, + tag: detail.status || "More", + onClick: () => { + const detailBody = document.createElement("div"); + const detailCard = document.createElement("div"); + detailCard.className = "card"; + (detail.members || []).forEach((member) => detailCard.appendChild(row({ + avatar: member.avatar, + title: member.userName, + sub: `ID: ${member.account || "-"}`, + side: `$${money(member.income || 0)}` + }))); + detailBody.appendChild(detailCard); + showModal(detail.settlementPeriod, detailBody); + } + })); + }); + if (!card.children.length) card.innerHTML = `
No data
`; + body.appendChild(card); + showModal(`${cfg.memberTitle} ${item.account || ""}`, body); + } catch (error) { + toast(error.message); + } +} + +async function renderRechargeAgency() { + setLoading(); + const [summary, list] = await Promise.allSettled([ + get("/app/h5/recharge-summary"), + get("/app/h5/recharge-page?cursor=1&limit=20") + ]); + const data = summary.value?.body || {}; + const records = list.value?.body?.records || []; + $("#app").innerHTML = `${metrics([ + { label: "My total income", value: `$${money(data.totalIncome || 0)}` }, + { label: "This period recharge", value: `$${money(data.currentMonthRecharge || 0)}` } + ])}
Recharge agency list
`; + const root = $("#records"); + records.forEach((item) => root.appendChild(row({ + avatar: item.userAvatar, + title: item.userNickname, + sub: `ID: ${item.account || "-"}`, + side: `This: $${money(item.thisMonth || 0)}\nLast: $${money(item.lastMonth || 0)}` + }))); + if (!root.children.length) root.innerHTML = `
No data
`; +} + +async function renderMessage() { + const teamId = getTeamId(); + setLoading(); + const res = await get(`/team/user/apply/record?teamId=${teamId}&status=WAIT`); + const records = (res.body || []).map((item) => ({ + id: item.id, + account: item.createUserProfile?.account, + name: item.createUserProfile?.userNickname, + avatar: item.createUserProfile?.userAvatar + })); + $("#app").innerHTML = `
`; + const root = $("#messages"); + records.forEach((item) => { + const node = row({ avatar: item.avatar, title: item.name || "Unknown user", sub: `ID: ${item.account || "-"}` }); + const actions = document.createElement("div"); + actions.className = "toolbar"; + actions.innerHTML = ``; + actions.children[0].addEventListener("click", async () => updateApply(item.id, "AGREE", node)); + actions.children[1].addEventListener("click", async () => updateApply(item.id, "REJECT", node)); + node.appendChild(actions); + root.appendChild(node); + }); + if (!root.children.length) root.innerHTML = `
No data
`; +} + +async function updateApply(id, status, node) { + try { + await post("/team/user/apply/record/update", { id, status }); + node.remove(); + toast(status === "AGREE" ? "Approved" : "Rejected"); + } catch (error) { + toast(error.message); + } +} + +async function renderTeamMembers() { + const teamId = getTeamId(); + setLoading(); + const res = await get(`/team/members?id=${teamId}`); + const records = res.body || []; + $("#app").innerHTML = ` +
+
`; + const root = $("#members"); + const draw = (items) => { + root.innerHTML = ""; + items.forEach((member, index) => root.appendChild(row({ + avatar: member.userProfile?.userAvatar, + title: `${index + 1}. ${member.userProfile?.userNickname || "-"}`, + sub: `ID: ${member.userProfile?.ownSpecialId?.account || member.userProfile?.account || "-"}`, + side: `${member.targetFormat || "-"}\n${member.timeHours || "-"}` + }))); + if (!root.children.length) root.innerHTML = `
No data
`; + }; + draw(records); + $("#searchBtn").addEventListener("click", async () => { + const account = $("#search").value.trim(); + if (!account) return draw(records); + try { + const searched = await get(`/team/search-members?account=${account}`); + draw(searched.body || []); + } catch (error) { + toast(error.message); + } + }); + $("#cancelBtn").addEventListener("click", () => { + $("#search").value = ""; + draw(records); + }); +} + +async function renderTeamBill() { + const teamId = getTeamId(); + setLoading(); + const res = await get(`/team/bill?id=${teamId}`); + const records = res.body || []; + let total = 0; + records.forEach((item) => { + if (item.status === "SETTLED") total += Number(item.settleResult?.totalSalary || 0); + }); + $("#app").innerHTML = `${metrics([{ label: "History team income", value: `$${money(total)}` }])}
Work report
`; + const root = $("#bills"); + records.forEach((item) => root.appendChild(row({ + title: item.billTitle, + sub: `Host salary: $${money(item.settleResult?.memberSalary || 0)} | Agent salary: $${money(item.settleResult?.ownSalary || 0)}`, + side: `$${money(item.settleResult?.totalSalary || 0)}`, + tag: ({ SETTLED: "Completed", UNPAID: "In Progress", HANG_UP: "Out of account", PAY_OUT: "Completed" })[item.status] || item.status, + onClick: async () => { + try { + const detail = await get(`/team/bill/work-members?id=${item.id}`); + const body = document.createElement("div"); + const card = document.createElement("div"); + card.className = "card"; + (detail.body || []).forEach((member) => card.appendChild(row({ + avatar: member.memberProfile?.userAvatar, + title: member.memberProfile?.userNickname, + sub: `ID: ${member.memberProfile?.account || "-"}`, + side: `Target: ${member.target?.acceptGiftValue || 0}\nDays: ${member.target?.effectiveDay || 0}` + }))); + body.appendChild(card); + showModal(item.billTitle, body); + } catch (error) { + toast(error.message); + } + } + }))); + if (!root.children.length) root.innerHTML = `
No data
`; +} + +function renderInviteProcess() { + $("#app").innerHTML = `
+
1
Invite users to download the app.
+
2
Invited users log in.
+
3
Users join your team.
+
4
Enter team ID when joining.
+
`; +} + +async function renderBankCards() { + setLoading(); + const [cards, kyc] = await Promise.allSettled([get("/wallet/bank-card/list"), get("/wallet/withdraw-info/list")]); + const current = (cards.value?.body || []).find((item) => item.use); + const info = kyc.value?.body?.[0]; + $("#app").innerHTML = ` +
Bank card
+
+
KYC
+
`; + $("#cardBox").appendChild(current ? row({ title: current.cardType, sub: `Card: ${current.cardNo}`, side: current.payee }) : row({ title: "Add payment method", sub: "No bank cards in use", tag: "Add", onClick: () => navigate("/bank-card") })); + $("#kycBox").appendChild(info ? row({ title: `Status: ${info.status}`, sub: `Contact: ${info.contactNumber || "-"}`, side: info.otherDescription || "", tag: "KYC", onClick: () => navigate("/KYC") }) : row({ title: "Complete information", sub: "No KYC info", tag: "KYC", onClick: () => navigate("/KYC") })); +} + +async function renderWalletAction() { + setLoading(); + const action = new URLSearchParams(location.search).get("activeAction") || cfg.action || "Exchange"; + const balanceEndpoint = cfg.salaryType ? `/wallet/salary-account/balance?salaryType=${cfg.salaryType}` : "/wallet/bank/balance"; + const balanceRes = await get(balanceEndpoint).catch(() => ({ body: 0 })); + const balance = typeof balanceRes.body === "number" ? balanceRes.body : balanceRes.body?.availableBalance || balanceRes.body?.balance || 0; + const amounts = [1, 5, 10, 50, 100, 200]; + $("#app").innerHTML = ` + ${metrics([{ label: "Available salaries", value: `$${money(balance)}` }])} +
+ + + +
+
`; + document.querySelectorAll("[data-act]").forEach((btn) => btn.addEventListener("click", () => navigate(`${location.pathname}?activeAction=${encodeURIComponent(btn.dataset.act)}&lang=${lang().toLowerCase()}`))); + const panel = $("#walletPanel"); + if (action === "Cash out") return renderCashOutPanel(panel); + if (action === "Transfer") return renderTransferPanel(panel, amounts); + renderExchangePanel(panel, amounts); +} + +function renderExchangePanel(panel, amounts) { + panel.innerHTML = `
${amounts.map((amount) => ``).join("")}
`; + let selected = null; + panel.querySelectorAll("[data-amount]").forEach((node) => node.addEventListener("click", () => { + selected = Number(node.dataset.amount); + panel.querySelectorAll(".tile").forEach((tile) => tile.classList.remove("selected")); + node.classList.add("selected"); + })); + $("#submit").addEventListener("click", async () => { + if (!selected) return toast("Please select amount"); + try { + const path = cfg.salaryType ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold"; + await post(path, cfg.salaryType ? { amount: selected, salaryType: cfg.salaryType } : { amount: selected }); + toast("Exchange success"); + } catch (error) { + toast(error.message); + } + }); +} + +function renderTransferPanel(panel, amounts) { + panel.innerHTML = `
${amounts.map((amount) => ``).join("")}
`; + let payee = null; + let selected = null; + $("#findPayee").addEventListener("click", async () => { + try { + const res = await get(`/user/user-profile/open-search?account=${encodeURIComponent($("#payee").value.trim())}`); + payee = res.body; + $("#payeeInfo").innerHTML = ""; + $("#payeeInfo").appendChild(row({ avatar: payee.userAvatar, title: payee.userNickname || payee.name, sub: `ID: ${payee.account}`, side: "" })); + } catch (error) { + toast(error.message); + } + }); + panel.querySelectorAll("[data-amount]").forEach((node) => node.addEventListener("click", () => { + selected = Number(node.dataset.amount); + panel.querySelectorAll(".tile").forEach((tile) => tile.classList.remove("selected")); + node.classList.add("selected"); + })); + $("#submit").addEventListener("click", async () => { + if (!selected || !payee?.id) return toast("Please select amount and payee"); + try { + const path = cfg.salaryType ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold"; + await post(path, cfg.salaryType ? { amount: selected, acceptUserId: payee.id, salaryType: cfg.salaryType } : { amount: selected, acceptUserId: payee.id, acceptMethod: "BANK_TRANSFER" }); + toast("Transfer success"); + } catch (error) { + toast(error.message); + } + }); +} + +async function renderCashOutPanel(panel) { + const [cards, kyc] = await Promise.allSettled([get("/wallet/bank-card/list"), get("/wallet/withdraw-info/list")]); + const card = (cards.value?.body || []).find((item) => item.use); + const info = kyc.value?.body?.[0]; + panel.innerHTML = `
`; + $("#card").appendChild(card ? row({ title: card.cardType, sub: `Card: ${card.cardNo}`, side: card.payee }) : row({ title: "No bank cards in use", sub: "Add bank card", tag: "Add", onClick: () => navigate("/bank-card") })); + $("#kyc").appendChild(info ? row({ title: `KYC: ${info.status}`, sub: `Contact: ${info.contactNumber || "-"}`, tag: "KYC" }) : row({ title: "Complete KYC", sub: "No KYC information", tag: "KYC", onClick: () => navigate("/KYC") })); + $("#submit").addEventListener("click", async () => { + const amount = Number($("#amount").value); + if (!card?.id || !amount || amount < 50) return toast("Please complete information and enter at least 50"); + try { + await post("/wallet/bank/withdraw", { amount, acceptBankCardId: card.id, salaryType: cfg.salaryType || (getIdentity() === "HOST" ? "HOST_SALARY" : "BD_SALARY") }); + toast("Cash withdrawal submitted"); + } catch (error) { + toast(error.message); + } + }); +} + +async function renderItemDistribution() { + setLoading(); + const permissionEndpoint = cfg.admin ? "/app/h5/own-permission?propsType=NOBLE_VIP" : "/sys/bd/list-permission"; + const propsBase = cfg.admin ? "/sys/manager/props" : "/sys/bd/props"; + const sendEndpoint = cfg.admin ? "/sys/manager/send" : "/sys/bd/send"; + let tabs = cfg.admin ? [{ name: "frames", type: "AVATAR_FRAME" }, { name: "vehicles", type: "RIDE" }, { name: "vip", type: "NOBLE_VIP" }] : []; + if (!cfg.admin) { + const res = await get(permissionEndpoint); + const body = res.body || []; + if (body.includes("AVATAR_FRAME")) tabs.push({ name: "frames", type: "AVATAR_FRAME" }); + if (body.includes("RIDE")) tabs.push({ name: "vehicles", type: "RIDE" }); + if (body.some((x) => String(x).startsWith("NOBLE_VIP"))) tabs.push({ name: "vip", type: "NOBLE_VIP" }); + } + if (!tabs.length) tabs = [{ name: "frames", type: "AVATAR_FRAME" }]; + $("#app").innerHTML = `
${tabs.map((tab, index) => ``).join("")}
`; + const load = async (tab) => { + const res = await get(`${propsBase}?propsType=${tab.type}¤cyType=GOLD`); + const root = $("#props"); + root.innerHTML = ""; + (res.body || []).forEach((item) => { + const tile = document.createElement("button"); + tile.type = "button"; + tile.className = "tile"; + tile.innerHTML = `${item.name || "Item"}Send`; + tile.addEventListener("click", () => openSendProps(item, sendEndpoint)); + root.appendChild(tile); + }); + if (!root.children.length) root.innerHTML = `
No data
`; + }; + document.querySelectorAll("[data-tab]").forEach((btn) => btn.addEventListener("click", () => { + document.querySelectorAll("[data-tab]").forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + load(tabs[Number(btn.dataset.tab)]); + })); + await load(tabs[0]); +} + +function openSendProps(item, endpoint) { + const body = document.createElement("div"); + body.innerHTML = `
`; + $("#confirmSend", body).addEventListener("click", async () => { + try { + const user = await get(`/user/user-profile/open-search?account=${encodeURIComponent($("#targetUser", body).value.trim())}`); + await post(endpoint, { propsId: item.id, acceptUserId: user.body.id }); + closeModal(); + toast("Gift delivery successful"); + } catch (error) { + toast(error.message); + } + }); + showModal(item.name || "Send", body, true); +} + +function renderPolicy() { + $("#app").innerHTML = ""; +} + +async function renderPlatformPolicy() { + const teamId = getTeamId(); + setLoading(); + const res = await get(`/team/release/policy?id=${teamId}`); + const policies = res.body?.policy || []; + $("#app").innerHTML = `
`; + const root = $("#policyList"); + policies.forEach((item) => root.appendChild(row({ + title: `Lv.${item.level}`, + sub: `Time: ${item.onlineTime || 0}h | Target: ${item.target || 0}`, + side: `Host: $${money(item.memberSalary || 0)}\nAgent: $${money(item.ownSalary || 0)}` + }))); + if (!root.children.length) root.innerHTML = `
No policy data available
`; +} + +async function init() { + renderShell(); + try { + await connectApp(); + const handlers = { + teamHistory: renderTeamHistory, + adminTeam: renderAdminTeam, + rechargeAgency: renderRechargeAgency, + message: renderMessage, + teamMembers: renderTeamMembers, + teamBill: renderTeamBill, + inviteProcess: renderInviteProcess, + bankCards: renderBankCards, + walletAction: renderWalletAction, + itemDistribution: renderItemDistribution, + platformPolicy: renderPlatformPolicy, + policy: renderPolicy + }; + await (handlers[cfg.mode] || renderPolicy)(); + } catch (error) { + console.error(error); + $("#app").innerHTML = `
${error.message || "Failed to load data"}
`; + } +} + +init(); diff --git a/h5/message/index.html b/h5/message/index.html new file mode 100644 index 0000000..1978f9a --- /dev/null +++ b/h5/message/index.html @@ -0,0 +1,13 @@ + + + + + + Message + + + + + + + diff --git a/h5/my-BD-teams/index.html b/h5/my-BD-teams/index.html new file mode 100644 index 0000000..b71207d --- /dev/null +++ b/h5/my-BD-teams/index.html @@ -0,0 +1,13 @@ + + + + + + My BD Teams + + + + + + + diff --git a/h5/my-BDLeader-teams/index.html b/h5/my-BDLeader-teams/index.html new file mode 100644 index 0000000..3f519d7 --- /dev/null +++ b/h5/my-BDLeader-teams/index.html @@ -0,0 +1,13 @@ + + + + + + My BD Leader Teams + + + + + + + diff --git a/h5/my-agency-teams/index.html b/h5/my-agency-teams/index.html new file mode 100644 index 0000000..afcaefc --- /dev/null +++ b/h5/my-agency-teams/index.html @@ -0,0 +1,13 @@ + + + + + + My Agency Teams + + + + + + + diff --git a/h5/my-recharge-agency/index.html b/h5/my-recharge-agency/index.html new file mode 100644 index 0000000..95bb679 --- /dev/null +++ b/h5/my-recharge-agency/index.html @@ -0,0 +1,13 @@ + + + + + + My Recharge Agency + + + + + + + diff --git a/h5/platform-policy/index.html b/h5/platform-policy/index.html new file mode 100644 index 0000000..396524e --- /dev/null +++ b/h5/platform-policy/index.html @@ -0,0 +1,13 @@ + + + + + + Platform Policy + + + + + + + diff --git a/h5/policy/index.html b/h5/policy/index.html new file mode 100644 index 0000000..8b18e28 --- /dev/null +++ b/h5/policy/index.html @@ -0,0 +1,13 @@ + + + + + + BD Policy + + + + + + + diff --git a/h5/search-payee/index.html b/h5/search-payee/index.html new file mode 100644 index 0000000..2ce7e86 --- /dev/null +++ b/h5/search-payee/index.html @@ -0,0 +1,13 @@ + + + + + + Search Payee + + + + + + + diff --git a/h5/team-bill/index.html b/h5/team-bill/index.html new file mode 100644 index 0000000..d1deec4 --- /dev/null +++ b/h5/team-bill/index.html @@ -0,0 +1,13 @@ + + + + + + Team Bill + + + + + + + diff --git a/h5/team-member/index.html b/h5/team-member/index.html new file mode 100644 index 0000000..3b2c760 --- /dev/null +++ b/h5/team-member/index.html @@ -0,0 +1,13 @@ + + + + + + Team Member + + + + + + + diff --git a/h5/transfer/index.html b/h5/transfer/index.html new file mode 100644 index 0000000..7d6a254 --- /dev/null +++ b/h5/transfer/index.html @@ -0,0 +1,13 @@ + + + + + + Transfer + + + + + + +