2026-06-04 19:29:48 +08:00

743 lines
25 KiB
JavaScript

(function () {
var AGENCY_SALARY_USD = 'AGENCY_SALARY_USD';
var HOST_SALARY_USD = 'HOST_SALARY_USD';
var mock = {
profile: {
name: 'Yumi Star Agency',
shortId: '163003',
avatar: avatarData('A', '#dbc8ff', '#7d57c7'),
bd: {
name: 'Mila BD',
shortId: '880001',
avatar: avatarData('M', '#efe4ff', '#7d57c7'),
},
},
metrics: {
salary: 12880.75,
},
hosts: [
{
id: '163000',
name: 'quick_1780052629537',
avatar: avatarData('Q', '#dbc8ff', '#7d57c7'),
workTime: 86,
gift: 2450,
salary: 3050.25,
},
{
id: '163011',
name: 'Lina_voice',
avatar: avatarData('L', '#f4ebff', '#7d57c7'),
workTime: 124,
gift: 3760,
salary: 4280.5,
},
{
id: '163024',
name: 'Nora_live',
avatar: avatarData('N', '#efe4ff', '#7d57c7'),
workTime: 98,
gift: 2990,
salary: 2298,
},
],
bills: [
{
month: '2026-06',
hostSalary: 8420.2,
agencySalary: 3260.55,
total: 11680.75,
status: 'In progress',
},
{
month: '2026-05',
hostSalary: 13240,
agencySalary: 5140.8,
total: 18380.8,
status: 'Completed',
},
],
messages: [
{
id: 'apply-1001',
applicationId: '1001',
name: 'Mona_host',
account: '163088',
avatar: avatarData('M', '#dbc8ff', '#7d57c7'),
},
{
id: 'apply-1002',
applicationId: '1002',
name: 'Sara_room',
account: '163099',
avatar: avatarData('S', '#f4ebff', '#7d57c7'),
},
],
};
var state = {
hosts: mock.hosts.slice(),
messages: mock.messages.slice(),
hostSearch: '',
pendingRemoveHost: null,
profile: mock.profile,
salary: mock.metrics.salary,
loading: false,
isMock: shouldUseMock(),
};
function $(id) {
return document.getElementById(id);
}
function shouldUseMock() {
var params = new URLSearchParams(window.location.search);
return params.get('mock') === '1' || params.get('mock') === 'true';
}
function avatarData(text, background, foreground) {
var letter = encodeURIComponent(String(text || 'A').charAt(0));
var svg =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">' +
'<rect width="96" height="96" rx="48" fill="' +
background +
'"/>' +
'<text x="50%" y="54%" text-anchor="middle" dominant-baseline="middle" fill="' +
foreground +
'" font-family="Arial, sans-serif" font-size="38" font-weight="800">' +
letter +
'</text>' +
'</svg>';
return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
}
function t(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback || key;
}
function toast(message) {
if (window.HyAppToast) {
window.HyAppToast.show(message);
return;
}
window.alert(message);
}
function formatMoney(value) {
var number = Number(value || 0);
return number.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
}
function formatCount(value) {
var number = Number(value || 0);
return number.toLocaleString('en-US', {
maximumFractionDigits: 2,
});
}
function toNumber(value) {
var number = Number(value || 0);
return Number.isFinite(number) ? number : 0;
}
function commandID(prefix) {
return (
prefix +
'-' +
Date.now().toString(36) +
'-' +
Math.random().toString(36).slice(2, 8)
);
}
function salaryDisplayAmount(salary, assetType) {
if (!salary) return 0;
if (
salary.display_amount !== undefined &&
salary.display_amount !== null
) {
return toNumber(salary.display_amount);
}
if (assetType === AGENCY_SALARY_USD || assetType === HOST_SALARY_USD) {
return toNumber(salary.available_amount) / 100;
}
return toNumber(salary.available_amount);
}
function initial(text) {
return String(text || '')
.trim()
.charAt(0)
.toUpperCase();
}
function setImage(image, fallback, src, name) {
if (!image || !fallback) return;
fallback.textContent = initial(name) || 'A';
if (!src) {
image.hidden = true;
fallback.hidden = false;
return;
}
image.onload = function () {
image.hidden = false;
fallback.hidden = true;
};
image.onerror = function () {
image.hidden = true;
fallback.hidden = false;
};
image.src = src;
}
function makeAvatar(item) {
var avatar = document.createElement('div');
var fallback = document.createElement('span');
avatar.className = 'small-avatar';
fallback.textContent = initial(item.name) || 'A';
avatar.appendChild(fallback);
if (item.avatar) {
var image = document.createElement('img');
image.src = item.avatar;
image.alt = '';
image.addEventListener('error', function () {
image.remove();
});
avatar.appendChild(image);
}
return avatar;
}
function hasRealAPI() {
return !state.isMock && window.HyAppAPI && window.HyAppAPI.agencyCenter;
}
function displayName(user, fallback) {
if (!user) return fallback || '-';
return (
user.username ||
user.display_user_id ||
user.user_id ||
fallback ||
'-'
);
}
function normalizeOverview(payload) {
var agency = payload && payload.agency ? payload.agency : {};
var bd = payload && payload.bd ? payload.bd : null;
state.profile = {
name: agency.name || mock.profile.name,
shortId: agency.agency_id || mock.profile.shortId,
avatar: agency.avatar || '',
bd: bd
? {
name: displayName(bd, ''),
shortId: bd.display_user_id || bd.user_id || '',
avatar: bd.avatar || '',
}
: null,
};
state.salary = salaryDisplayAmount(
payload ? payload.salary : null,
AGENCY_SALARY_USD
);
}
function normalizeHost(item) {
var userId = String(item.host_user_id || item.user_id || item.id || '');
var displayId = String(item.display_user_id || userId);
return {
id: displayId,
userId: userId,
name: item.username || displayId || userId || '-',
avatar: item.avatar || '',
salary: salaryDisplayAmount(item.salary, HOST_SALARY_USD),
};
}
function normalizeMessage(item) {
var applicant = item.applicant || {};
var applicantUserId = String(
item.applicant_user_id || applicant.user_id || ''
);
return {
id: String(item.application_id || item.id || ''),
applicationId: String(item.application_id || item.id || ''),
name: displayName(applicant, applicantUserId),
account: applicant.display_user_id || applicantUserId,
avatar: applicant.avatar || '',
};
}
function applyAgencyCenterPayload(overview, hosts, applications) {
normalizeOverview(overview || {});
state.hosts = ((hosts && hosts.items) || []).map(normalizeHost);
state.messages = ((applications && applications.items) || []).map(
normalizeMessage
);
}
function loadAgencyCenter() {
if (!hasRealAPI()) return Promise.resolve();
if (window.HyAppParams && window.HyAppParams.parse) {
window.HyAppParams.parse();
}
state.loading = true;
return Promise.all([
window.HyAppAPI.agencyCenter.overview(),
window.HyAppAPI.agencyCenter.hosts(),
window.HyAppAPI.agencyCenter.applications('pending'),
])
.then(function (results) {
applyAgencyCenterPayload(results[0], results[1], results[2]);
render();
})
.catch(function () {
toast(
t(
'agency_center.load_failed',
'Load failed. Try again later.'
)
);
})
.finally(function () {
state.loading = false;
});
}
function renderProfile() {
var profile = state.profile || mock.profile;
var bd = profile.bd || null;
var bdRow = $('bdRow');
$('agencyName').textContent = profile.name;
$('agencyShortId').textContent = t(
'agency_center.agency_short_id',
'ID: {id}'
).replace('{id}', profile.shortId || '-');
if (bd && bd.shortId) {
bdRow.hidden = false;
$('bdShortId').textContent = bd.shortId;
setImage($('bdAvatar'), $('bdAvatarFallback'), bd.avatar, bd.name);
} else {
bdRow.hidden = true;
$('bdShortId').textContent = '';
}
setImage(
$('agencyAvatar'),
$('agencyAvatarFallback'),
profile.avatar,
profile.name
);
}
function renderSalary() {
$('currentSalary').textContent = formatMoney(state.salary);
}
function renderHostCount() {
$('hostCount').textContent = formatCount(state.hosts.length);
}
function renderUnread() {
var dot = $('messageUnreadDot');
if (dot) dot.hidden = state.messages.length <= 0;
}
function filteredHosts() {
var keyword = state.hostSearch.trim();
if (!keyword) return state.hosts;
return state.hosts.filter(function (host) {
return (
host.id.indexOf(keyword) >= 0 ||
String(host.userId || '').indexOf(keyword) >= 0 ||
host.name.toLowerCase().indexOf(keyword.toLowerCase()) >= 0
);
});
}
function renderHostList() {
var list = $('hostList');
var empty = $('hostListEmpty');
var hosts = filteredHosts();
list.textContent = '';
empty.hidden = hosts.length > 0;
empty.textContent = t('agency_center.no_hosts', 'No hosts');
hosts.forEach(function (host) {
var row = document.createElement('div');
var user = document.createElement('div');
var copy = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var remove = document.createElement('button');
row.className = 'host-row';
user.className = 'host-user';
copy.className = 'host-copy';
name.className = 'host-name';
meta.className = 'host-meta';
remove.className = 'remove-host-button';
remove.type = 'button';
name.textContent = host.name;
meta.textContent =
'UID: ' +
host.id +
' · ' +
t('agency_center.salary', 'Salary') +
': ' +
'$' +
formatMoney(host.salary);
remove.textContent = t('agency_center.remove', 'Remove');
remove.addEventListener('click', function () {
state.pendingRemoveHost = host;
renderRemoveConfirm();
openSheet('removeConfirmModal');
});
user.appendChild(makeAvatar(host));
copy.appendChild(name);
copy.appendChild(meta);
user.appendChild(copy);
row.appendChild(user);
row.appendChild(remove);
list.appendChild(row);
});
}
function renderRemoveConfirm() {
var host = state.pendingRemoveHost;
var text = $('removeConfirmText');
if (!text) return;
text.textContent = t(
'agency_center.remove_confirm_message',
'Confirm remove {name} from your agency?'
).replace('{name}', host ? host.name : '-');
}
function confirmRemoveHost() {
var host = state.pendingRemoveHost;
if (!host) return;
if (hasRealAPI()) {
window.HyAppAPI.agencyCenter
.removeHost(host.userId || host.id, {
command_id: commandID('agency-remove-host'),
reason: 'agency_center',
})
.then(function () {
state.pendingRemoveHost = null;
closeSheet('removeConfirmModal');
toast(t('agency_center.host_removed', 'Host removed.'));
return loadAgencyCenter();
})
.catch(function (error) {
if (error && error.status === 409) return;
toast(
t(
'agency_center.remove_failed',
'Remove failed. Try again later.'
)
);
});
return;
}
state.hosts = state.hosts.filter(function (item) {
return item.id !== host.id;
});
state.pendingRemoveHost = null;
closeSheet('removeConfirmModal');
renderHostCount();
renderHostList();
toast(t('agency_center.host_removed', 'Host removed.'));
}
function createBillCell(label, value) {
var cell = document.createElement('div');
var title = document.createElement('span');
var strong = document.createElement('strong');
cell.className = 'bill-cell';
title.textContent = label;
strong.textContent = value;
cell.appendChild(title);
cell.appendChild(strong);
return cell;
}
function renderBillList() {
var list = $('billList');
list.textContent = '';
mock.bills.forEach(function (bill) {
var row = document.createElement('div');
var month = document.createElement('div');
var values = document.createElement('div');
row.className = 'bill-row';
month.className = 'bill-month';
values.className = 'bill-values';
month.textContent = bill.month + ' · ' + bill.status;
values.appendChild(
createBillCell(
t('agency_center.host_salary', 'Host salary'),
formatMoney(bill.hostSalary)
)
);
values.appendChild(
createBillCell(
t('agency_center.agency_salary', 'Agency salary'),
formatMoney(bill.agencySalary)
)
);
values.appendChild(
createBillCell(
t('agency_center.total', 'Total'),
formatMoney(bill.total)
)
);
row.appendChild(month);
row.appendChild(values);
list.appendChild(row);
});
}
function renderMessages() {
var list = $('messageList');
var empty = $('messageEmpty');
list.textContent = '';
empty.hidden = state.messages.length > 0;
empty.textContent = t('agency_center.no_messages', 'No messages');
state.messages.forEach(function (message) {
var card = document.createElement('div');
var user = document.createElement('div');
var copy = document.createElement('div');
var name = document.createElement('div');
var id = document.createElement('div');
var actions = document.createElement('div');
var agree = document.createElement('button');
var refuse = document.createElement('button');
card.className = 'message-card';
user.className = 'message-user';
copy.className = 'message-copy';
name.className = 'message-name';
id.className = 'message-id';
actions.className = 'message-actions';
agree.className = 'message-action agree';
refuse.className = 'message-action refuse';
agree.type = 'button';
refuse.type = 'button';
name.textContent = message.name;
id.textContent = 'ID: ' + message.account;
agree.textContent = t('agency_center.agree', 'Agree');
refuse.textContent = t('agency_center.refuse', 'Refuse');
agree.addEventListener('click', function () {
processMessage(message, 'agree');
});
refuse.addEventListener('click', function () {
processMessage(message, 'refuse');
});
user.appendChild(makeAvatar(message));
copy.appendChild(name);
copy.appendChild(id);
user.appendChild(copy);
actions.appendChild(agree);
actions.appendChild(refuse);
card.appendChild(user);
card.appendChild(actions);
list.appendChild(card);
});
}
function processMessage(message, action) {
if (hasRealAPI()) {
var decision = action === 'agree' ? 'approve' : 'reject';
window.HyAppAPI.agencyCenter
.reviewApplication(message.applicationId || message.id, {
command_id: commandID('agency-review-application'),
decision: decision,
reason: 'agency_center',
})
.then(function () {
var key =
action === 'agree'
? 'agency_center.approved_application'
: 'agency_center.rejected_application';
var fallback =
action === 'agree'
? "Approved {name}'s application."
: "Rejected {name}'s application.";
toast(t(key, fallback).replace('{name}', message.name));
return loadAgencyCenter();
})
.catch(function (error) {
if (error && error.status === 409) return;
toast(
t(
'agency_center.review_failed',
'Review failed. Try again later.'
)
);
});
return;
}
state.messages = state.messages.filter(function (item) {
return item.id !== message.id;
});
renderUnread();
renderMessages();
var key =
action === 'agree'
? 'agency_center.approved_application'
: 'agency_center.rejected_application';
var fallback =
action === 'agree'
? "Approved {name}'s application."
: "Rejected {name}'s application.";
toast(t(key, fallback).replace('{name}', message.name));
}
function openSheet(id) {
var sheet = $(id);
if (!sheet) return;
sheet.hidden = false;
sheet.setAttribute('aria-hidden', 'false');
document.body.classList.add('modal-open');
}
function closeSheet(id) {
var sheet = $(id);
if (!sheet) return;
sheet.hidden = true;
sheet.setAttribute('aria-hidden', 'true');
var hasOpen = Array.prototype.some.call(
document.querySelectorAll(
'.sheet-modal, .message-modal, .confirm-modal'
),
function (modal) {
return !modal.hidden;
}
);
document.body.classList.toggle('modal-open', hasOpen);
}
function closeAllSheets() {
document
.querySelectorAll('.sheet-modal, .message-modal, .confirm-modal')
.forEach(function (sheet) {
sheet.hidden = true;
sheet.setAttribute('aria-hidden', 'true');
});
state.pendingRemoveHost = null;
document.body.classList.remove('modal-open');
}
function navigateToWithdrawExchange() {
var params = new URLSearchParams(window.location.search);
if (!params.has('mode')) params.set('mode', 'transfer');
window.location.href =
'../withdraw-exchange/index.html?' + params.toString();
}
function bindEvents() {
$('backButton').addEventListener('click', function () {
if (window.HyAppBridge) window.HyAppBridge.back();
});
$('messageButton').addEventListener('click', function () {
renderMessages();
openSheet('messageModal');
});
$('withdrawExchangeButton').addEventListener(
'click',
navigateToWithdrawExchange
);
$('salaryValueButton').addEventListener(
'click',
navigateToWithdrawExchange
);
$('hostSearchForm').addEventListener('submit', function (event) {
event.preventDefault();
state.hostSearch = $('hostSearchInput').value || '';
renderHostList();
});
$('confirmRemoveButton').addEventListener('click', confirmRemoveHost);
document
.querySelectorAll('[data-close-sheet]')
.forEach(function (node) {
node.addEventListener('click', function () {
if (
node.getAttribute('data-close-sheet') ===
'removeConfirmModal'
) {
state.pendingRemoveHost = null;
}
closeSheet(node.getAttribute('data-close-sheet'));
});
});
document.querySelectorAll('.menu-row').forEach(function (button) {
button.addEventListener('click', function () {
var action = button.getAttribute('data-action');
if (action === 'host-list') {
state.hostSearch = '';
$('hostSearchInput').value = '';
renderHostList();
openSheet('hostSheet');
return;
}
if (action === 'team-bill') {
renderBillList();
openSheet('billSheet');
return;
}
if (action === 'invite') {
openSheet('inviteSheet');
return;
}
toast(
t(
'agency_center.mock_navigation',
'Mock navigation: {label}'
).replace(
'{label}',
t('agency_center.agency_policy', 'Agency Policy')
)
);
});
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') closeAllSheets();
});
}
function render() {
renderProfile();
renderSalary();
renderHostCount();
renderUnread();
renderHostList();
renderBillList();
renderMessages();
}
function initPage() {
render();
bindEvents();
loadAgencyCenter();
if (window.HyAppBridge) {
window.HyAppBridge.ready({
page: 'agency-center',
mock: state.isMock,
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPage);
} else {
initPage();
}
window.addEventListener('hyapp:i18n-ready', function () {
render();
});
})();