chatapp-h5/h5/yumi-invite-recharge-agency/yumi-invite-recharge-agency.js
2026-04-24 21:13:53 +08:00

700 lines
21 KiB
JavaScript

(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.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 =
'<div style="background-image: linear-gradient(112deg, #759cff 5.66%, #3d73ff 42.49%); padding: 4px 8px; border-radius: 0 12px; color: white; font-weight: 600">' +
TEXT.pending +
"</div>";
} else if (item.status === 1) {
statusBadge.innerHTML =
'<div style="background-image: linear-gradient(112deg, #75ff98 5.66%, #3dff54 42.49%); padding: 4px 8px; border-radius: 0 12px; color: white; font-weight: 600">' +
TEXT.success +
"</div>";
}
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 =
'<div class="modal modal-enter modal-' +
config.type +
'">' +
(config.type === "simple"
? ""
: '<div class="modal-icon"><div class="icon-circle icon-circle-' +
config.type +
'"><span class="icon-symbol icon-symbol-' +
config.type +
'">' +
iconMap[config.type] +
"</span></div></div>") +
(config.type === "simple" || !config.title
? ""
: '<div class="modal-title">' + escapeHtml(config.title) + "</div>") +
'<div class="modal-message">' +
escapeHtml(message) +
"</div>" +
'<div class="modal-actions"><button class="modal-btn modal-btn-' +
config.type +
'">' +
escapeHtml(config.confirmText) +
"</button></div>" +
"</div>";
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
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);
}
})();