agency center

This commit is contained in:
zhx 2026-06-05 15:09:35 +08:00
parent 527484d3d8
commit da6d029a27
4 changed files with 758 additions and 364 deletions

View File

@ -331,6 +331,11 @@ var default_api = 'https://api.global-interaction.com/';
method: 'GET', method: 'GET',
}); });
}, },
platformPolicy: function () {
return request('/api/v1/agency-center/platform-policy', {
method: 'GET',
});
},
hosts: function () { hosts: function () {
return request('/api/v1/agency-center/hosts', { return request('/api/v1/agency-center/hosts', {
method: 'GET', method: 'GET',

View File

@ -9,7 +9,10 @@
<title>Agency Center</title> <title>Agency Center</title>
<link rel="stylesheet" href="../../common/theme.css" /> <link rel="stylesheet" href="../../common/theme.css" />
<link rel="stylesheet" href="../common/center.css" /> <link rel="stylesheet" href="../common/center.css" />
<link rel="stylesheet" href="./style.css" /> <link
rel="stylesheet"
href="./style.css?v=20260605-agency-real-policy"
/>
</head> </head>
<body> <body>
@ -64,8 +67,8 @@
</div> </div>
</div> </div>
<div class="profile-copy"> <div class="profile-copy">
<div class="name" id="agencyName">Yumi Star Agency</div> <div class="name" id="agencyName">-</div>
<div class="meta" id="agencyShortId">ID: 163003</div> <div class="meta" id="agencyShortId">ID: -</div>
<div class="bd-row" id="bdRow" hidden> <div class="bd-row" id="bdRow" hidden>
<span <span
class="bd-label" class="bd-label"
@ -86,9 +89,7 @@
>B</span >B</span
> >
</span> </span>
<span class="bd-short-id" id="bdShortId" <span class="bd-short-id" id="bdShortId"></span>
>880001</span
>
</div> </div>
</div> </div>
<button <button
@ -169,24 +170,6 @@
<span class="menu-count" id="hostCount">0</span> <span class="menu-count" id="hostCount">0</span>
<span class="chevron" aria-hidden="true">&rsaquo;</span> <span class="chevron" aria-hidden="true">&rsaquo;</span>
</button> </button>
<button
class="menu-row"
type="button"
data-action="team-bill"
>
<span class="menu-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M6 3h12v18H6Z" />
<path d="M9 8h6M9 12h5M9 16h6" />
</svg>
</span>
<span
class="menu-label"
data-i18n="agency_center.salary_bills"
>Salary Bills</span
>
<span class="chevron" aria-hidden="true">&rsaquo;</span>
</button>
<button class="menu-row" type="button" data-action="policy"> <button class="menu-row" type="button" data-action="policy">
<span class="menu-icon" aria-hidden="true"> <span class="menu-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"> <svg viewBox="0 0 24 24">
@ -316,38 +299,57 @@
</section> </section>
</div> </div>
<div class="sheet-modal" id="billSheet" hidden aria-hidden="true"> <div
class="sheet-modal"
id="policyModal"
hidden
aria-hidden="true"
>
<button <button
class="modal-backdrop" class="modal-backdrop"
type="button" type="button"
data-close-sheet="billSheet" data-close-policy
aria-label="Close" aria-label="Close"
data-i18n-aria="agency_center.close" data-i18n-aria="agency_center.close"
></button> ></button>
<section <section
class="bottom-sheet" class="bottom-sheet policy-sheet"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="billTitle" aria-labelledby="policyTitle"
> >
<div class="sheet-head"> <div class="sheet-head">
<h2 <h2
id="billTitle" id="policyTitle"
data-i18n="agency_center.salary_bills" data-i18n="agency_center.agency_policy"
> >
Salary Bills Agency Policy
</h2> </h2>
<button <button
class="sheet-close" class="sheet-close"
type="button" type="button"
data-close-sheet="billSheet" data-close-policy
aria-label="Close" aria-label="Close"
data-i18n-aria="agency_center.close" data-i18n-aria="agency_center.close"
> >
&times; &times;
</button> </button>
</div> </div>
<div class="bill-list" id="billList"></div> <div class="policy-sheet-body">
<div class="policy-summary">
<strong id="policyName">Current policy</strong>
<span id="policyMeta"></span>
</div>
<div
class="policy-empty"
id="policyEmpty"
hidden
></div>
<div
class="policy-level-list"
id="policyLevelList"
></div>
</div>
</section> </section>
</div> </div>
@ -454,10 +456,10 @@
</div> </div>
<script src="../../common/toast.js"></script> <script src="../../common/toast.js"></script>
<script src="../../common/api.js?v=20260603-agency-center"></script> <script src="../../common/api.js?v=20260605-agency-real-policy"></script>
<script src="../../common/params.js?v=20260603-agency-center"></script> <script src="../../common/params.js?v=20260603-agency-center"></script>
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/i18n.js"></script> <script src="../../common/i18n.js"></script>
<script src="./script.js"></script> <script src="./script.js?v=20260605-agency-real-policy"></script>
</body> </body>
</html> </html>

View File

@ -2,98 +2,45 @@
var AGENCY_SALARY_USD = 'AGENCY_SALARY_USD'; var AGENCY_SALARY_USD = 'AGENCY_SALARY_USD';
var HOST_SALARY_USD = 'HOST_SALARY_USD'; var HOST_SALARY_USD = 'HOST_SALARY_USD';
var mock = { var state = emptyState();
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 = { function emptyState() {
hosts: mock.hosts.slice(), return {
messages: mock.messages.slice(), hosts: [],
messages: [],
hostSearch: '', hostSearch: '',
pendingRemoveHost: null, pendingRemoveHost: null,
profile: mock.profile, profile: {
salary: mock.metrics.salary, loading: true,
loading: false, name: '',
isMock: shouldUseMock(), shortId: '',
avatar: '',
bd: null,
},
salary: {
loading: true,
available: 0,
},
policy: {
loading: true,
loaded: false,
found: false,
policy: null,
},
loading: true,
}; };
}
function $(id) { function $(id) {
return document.getElementById(id); return document.getElementById(id);
} }
function shouldUseMock() { function api() {
var params = new URLSearchParams(window.location.search); return window.HyAppAPI || {};
return params.get('mock') === '1' || params.get('mock') === 'true'; }
function agencyAPI() {
return api().agencyCenter || null;
} }
function avatarData(text, background, foreground) { function avatarData(text, background, foreground) {
@ -119,6 +66,7 @@
} }
function toast(message) { function toast(message) {
if (!message) return;
if (window.HyAppToast) { if (window.HyAppToast) {
window.HyAppToast.show(message); window.HyAppToast.show(message);
return; return;
@ -137,7 +85,7 @@
function formatCount(value) { function formatCount(value) {
var number = Number(value || 0); var number = Number(value || 0);
return number.toLocaleString('en-US', { return number.toLocaleString('en-US', {
maximumFractionDigits: 2, maximumFractionDigits: 0,
}); });
} }
@ -156,6 +104,24 @@
); );
} }
function firstValue(source, keys) {
if (!source) return '';
for (var i = 0; i < keys.length; i += 1) {
var value = source[keys[i]];
if (value !== undefined && value !== null && value !== '') {
return value;
}
}
return '';
}
function unwrapData(payload) {
if (!payload || typeof payload !== 'object') return payload;
return Object.prototype.hasOwnProperty.call(payload, 'data')
? payload.data
: payload;
}
function salaryDisplayAmount(salary, assetType) { function salaryDisplayAmount(salary, assetType) {
if (!salary) return 0; if (!salary) return 0;
if ( if (
@ -177,11 +143,66 @@
.toUpperCase(); .toUpperCase();
} }
function currentLocale() {
var lang =
window.HyAppI18n && window.HyAppI18n.lang
? window.HyAppI18n.lang()
: 'en';
var map = {
en: 'en-US',
ar: 'ar',
tr: 'tr-TR',
es: 'es-ES',
zh: 'zh-CN',
id: 'id-ID',
};
return map[lang] || 'en-US';
}
function formatMode(value) {
var text = String(value || '')
.replace(/[_-]+/g, ' ')
.trim();
if (!text) return '-';
return text.replace(/\b\w/g, function (letter) {
return letter.toUpperCase();
});
}
function formatDate(ms) {
var value = toNumber(ms);
if (!value) return '';
try {
return new Date(value).toLocaleDateString(currentLocale(), {
year: 'numeric',
month: 'short',
day: 'numeric',
});
} catch (_) {
return '';
}
}
function policyAmount(minor) {
return '$' + formatMoney(toNumber(minor) / 100);
}
function setSkeleton(element, loading, text) {
if (!element) return;
element.classList.toggle('skeleton-line', Boolean(loading));
if (loading) {
element.textContent = '';
return;
}
element.textContent = text || '';
}
function setImage(image, fallback, src, name) { function setImage(image, fallback, src, name) {
if (!image || !fallback) return; if (!image || !fallback) return;
fallback.textContent = initial(name) || 'A'; fallback.textContent = initial(name) || 'A';
if (!src) { if (!src) {
image.hidden = true; image.hidden = true;
image.removeAttribute('src');
fallback.hidden = false; fallback.hidden = false;
return; return;
} }
@ -196,6 +217,16 @@
image.src = src; image.src = src;
} }
function setAvatarSkeleton(image, fallback, loading) {
if (!image || !fallback) return;
fallback.classList.toggle('avatar-skeleton', Boolean(loading));
if (!loading) return;
image.hidden = true;
image.removeAttribute('src');
fallback.hidden = false;
fallback.textContent = '';
}
function makeAvatar(item) { function makeAvatar(item) {
var avatar = document.createElement('div'); var avatar = document.createElement('div');
var fallback = document.createElement('span'); var fallback = document.createElement('span');
@ -214,40 +245,50 @@
return avatar; return avatar;
} }
function hasRealAPI() {
return !state.isMock && window.HyAppAPI && window.HyAppAPI.agencyCenter;
}
function displayName(user, fallback) { function displayName(user, fallback) {
if (!user) return fallback || '-'; if (!user) return fallback || '-';
return ( return (
user.username || firstValue(user, ['username', 'name', 'display_user_id']) ||
user.display_user_id ||
user.user_id ||
fallback || fallback ||
'-' '-'
); );
} }
function normalizeOverview(payload) { function normalizeOverview(payload) {
var agency = payload && payload.agency ? payload.agency : {}; var data = unwrapData(payload) || {};
var bd = payload && payload.bd ? payload.bd : null; var agency = data.agency || {};
var bd = data.bd || null;
state.profile = { state.profile = {
name: agency.name || mock.profile.name, loading: false,
shortId: agency.agency_id || mock.profile.shortId, name: agency.name || '-',
avatar: agency.avatar || '', shortId:
firstValue(agency, [
'agency_id',
'agencyId',
'short_id',
'shortId',
'display_user_id',
]) || '-',
avatar:
agency.avatar ||
avatarData(initial(agency.name) || 'A', '#dbc8ff', '#7d57c7'),
bd: bd bd: bd
? { ? {
name: displayName(bd, ''), name: displayName(bd, ''),
shortId: bd.display_user_id || bd.user_id || '', shortId: firstValue(bd, [
'display_user_id',
'displayUserID',
'user_id',
'userId',
]),
avatar: bd.avatar || '', avatar: bd.avatar || '',
} }
: null, : null,
}; };
state.salary = salaryDisplayAmount( state.salary = {
payload ? payload.salary : null, loading: false,
AGENCY_SALARY_USD available: salaryDisplayAmount(data.salary, AGENCY_SALARY_USD),
); };
} }
function normalizeHost(item) { function normalizeHost(item) {
@ -276,51 +317,64 @@
}; };
} }
function applyAgencyCenterPayload(overview, hosts, applications) { function applyPolicy(payload) {
normalizeOverview(overview || {}); var data = unwrapData(payload);
state.hosts = ((hosts && hosts.items) || []).map(normalizeHost); if (!data || !data.found || !data.policy) {
state.messages = ((applications && applications.items) || []).map( state.policy = {
normalizeMessage loading: false,
); loaded: true,
found: false,
policy: null,
};
return;
}
state.policy = {
loading: false,
loaded: true,
found: true,
policy: data.policy,
};
} }
function loadAgencyCenter() { function markLoadedEmpty() {
if (!hasRealAPI()) return Promise.resolve(); state.profile = {
if (window.HyAppParams && window.HyAppParams.parse) { loading: false,
window.HyAppParams.parse(); name: '-',
} shortId: '-',
state.loading = true; avatar: '',
return Promise.all([ bd: null,
window.HyAppAPI.agencyCenter.overview(), };
window.HyAppAPI.agencyCenter.hosts(), state.salary = { loading: false, available: 0 };
window.HyAppAPI.agencyCenter.applications('pending'), state.hosts = [];
]) state.messages = [];
.then(function (results) { state.policy = {
applyAgencyCenterPayload(results[0], results[1], results[2]); loading: false,
render(); loaded: true,
}) found: false,
.catch(function () { policy: null,
toast( };
t(
'agency_center.load_failed',
'Load failed. Try again later.'
)
);
})
.finally(function () {
state.loading = false; state.loading = false;
});
} }
function renderProfile() { function renderProfile() {
var profile = state.profile || mock.profile; var profile = state.profile;
var bd = profile.bd || null; var bd = profile.bd || null;
var bdRow = $('bdRow'); var bdRow = $('bdRow');
$('agencyName').textContent = profile.name; var loading = Boolean(profile.loading);
$('agencyShortId').textContent = t( setSkeleton($('agencyName'), loading, profile.name || '-');
'agency_center.agency_short_id', setSkeleton(
'ID: {id}' $('agencyShortId'),
).replace('{id}', profile.shortId || '-'); loading,
t('agency_center.agency_short_id', 'ID: {id}').replace(
'{id}',
profile.shortId || '-'
)
);
setAvatarSkeleton($('agencyAvatar'), $('agencyAvatarFallback'), loading);
if (loading) {
bdRow.hidden = true;
return;
}
if (bd && bd.shortId) { if (bd && bd.shortId) {
bdRow.hidden = false; bdRow.hidden = false;
$('bdShortId').textContent = bd.shortId; $('bdShortId').textContent = bd.shortId;
@ -338,11 +392,22 @@
} }
function renderSalary() { function renderSalary() {
$('currentSalary').textContent = formatMoney(state.salary); var loading = Boolean(state.salary.loading);
var currency = document.querySelector('.salary-currency');
if (currency) currency.classList.toggle('skeleton-coin', loading);
setSkeleton(
$('currentSalary'),
loading,
formatMoney(state.salary.available)
);
} }
function renderHostCount() { function renderHostCount() {
$('hostCount').textContent = formatCount(state.hosts.length); setSkeleton(
$('hostCount'),
Boolean(state.loading),
formatCount(state.hosts.length)
);
} }
function renderUnread() { function renderUnread() {
@ -362,11 +427,42 @@
}); });
} }
function skeletonHostRow() {
var row = document.createElement('div');
var user = document.createElement('div');
var avatar = document.createElement('div');
var copy = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var side = document.createElement('div');
row.className = 'host-row is-skeleton';
user.className = 'host-user';
avatar.className = 'small-avatar avatar-skeleton';
copy.className = 'host-copy';
name.className = 'host-name skeleton-line';
meta.className = 'host-meta skeleton-line';
side.className = 'host-side-skeleton skeleton-line';
copy.appendChild(name);
copy.appendChild(meta);
user.appendChild(avatar);
user.appendChild(copy);
row.appendChild(user);
row.appendChild(side);
return row;
}
function renderHostList() { function renderHostList() {
var list = $('hostList'); var list = $('hostList');
var empty = $('hostListEmpty'); var empty = $('hostListEmpty');
var hosts = filteredHosts(); var hosts = filteredHosts();
list.textContent = ''; list.textContent = '';
if (state.loading) {
empty.hidden = true;
list.appendChild(skeletonHostRow());
list.appendChild(skeletonHostRow());
list.appendChild(skeletonHostRow());
return;
}
empty.hidden = hosts.length > 0; empty.hidden = hosts.length > 0;
empty.textContent = t('agency_center.no_hosts', 'No hosts'); empty.textContent = t('agency_center.no_hosts', 'No hosts');
hosts.forEach(function (host) { hosts.forEach(function (host) {
@ -420,9 +516,12 @@
function confirmRemoveHost() { function confirmRemoveHost() {
var host = state.pendingRemoveHost; var host = state.pendingRemoveHost;
if (!host) return; var service = agencyAPI();
if (hasRealAPI()) { if (!host || !service || !service.removeHost) {
window.HyAppAPI.agencyCenter toast(t('agency_center.api_not_ready', 'API module is not ready.'));
return;
}
service
.removeHost(host.userId || host.id, { .removeHost(host.userId || host.id, {
command_id: commandID('agency-remove-host'), command_id: commandID('agency-remove-host'),
reason: 'agency_center', reason: 'agency_center',
@ -442,63 +541,6 @@
) )
); );
}); });
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() { function renderMessages() {
@ -549,9 +591,13 @@
} }
function processMessage(message, action) { function processMessage(message, action) {
if (hasRealAPI()) { var service = agencyAPI();
if (!service || !service.reviewApplication) {
toast(t('agency_center.api_not_ready', 'API module is not ready.'));
return;
}
var decision = action === 'agree' ? 'approve' : 'reject'; var decision = action === 'agree' ? 'approve' : 'reject';
window.HyAppAPI.agencyCenter service
.reviewApplication(message.applicationId || message.id, { .reviewApplication(message.applicationId || message.id, {
command_id: commandID('agency-review-application'), command_id: commandID('agency-review-application'),
decision: decision, decision: decision,
@ -578,22 +624,113 @@
) )
); );
}); });
}
function appendPolicyCell(parent, label, value) {
var cell = document.createElement('div');
var labelNode = document.createElement('span');
var valueNode = document.createElement('strong');
cell.className = 'policy-cell';
labelNode.className = 'policy-cell-label';
labelNode.textContent = label;
valueNode.className = 'policy-cell-value';
valueNode.textContent = value;
cell.appendChild(labelNode);
cell.appendChild(valueNode);
parent.appendChild(cell);
}
function renderPolicy() {
var summary = state.policy || {};
var policy = summary.policy || {};
var list = $('policyLevelList');
var empty = $('policyEmpty');
if (!list || !empty) return;
list.textContent = '';
if (summary.loading) {
setSkeleton($('policyName'), true, '');
setSkeleton($('policyMeta'), true, '');
empty.hidden = false;
empty.textContent = '';
empty.classList.add('policy-skeleton-block');
return; return;
} }
state.messages = state.messages.filter(function (item) { $('policyName').classList.remove('skeleton-line');
return item.id !== message.id; $('policyMeta').classList.remove('skeleton-line');
empty.classList.remove('policy-skeleton-block');
if (!summary.found || !policy || !policy.name) {
$('policyName').textContent = t(
'agency_center.agency_policy',
'Agency Policy'
);
$('policyMeta').textContent = '';
empty.hidden = false;
empty.textContent = 'No agency policy configured.';
return;
}
var from = formatDate(policy.effective_from_ms);
var to = formatDate(policy.effective_to_ms);
var meta = [
'Status: ' + formatMode(policy.status),
'Mode: ' + formatMode(policy.settlement_mode),
'Trigger: ' + formatMode(policy.settlement_trigger_mode),
'Gift ratio: ' + (policy.gift_coin_to_diamond_ratio || '-'),
'Residual rate: ' + (policy.residual_diamond_to_usd_rate || '-'),
];
if (from || to) {
meta.push('Effective: ' + (from || '-') + ' - ' + (to || 'No end'));
}
$('policyName').textContent = policy.name || 'Current policy';
$('policyMeta').textContent = meta.join(' / ');
empty.hidden = true;
empty.textContent = '';
var levels = Array.isArray(policy.levels) ? policy.levels : [];
if (!levels.length) {
empty.hidden = false;
empty.textContent = 'No policy levels configured.';
return;
}
levels.forEach(function (level) {
var row = document.createElement('div');
var head = document.createElement('div');
var title = document.createElement('strong');
var required = document.createElement('span');
var grid = document.createElement('div');
var levelNo = toNumber(level.level_no);
row.className = 'policy-level-row';
head.className = 'policy-row-head';
title.className = 'policy-level';
required.className = 'policy-effective';
grid.className = 'policy-grid';
title.textContent = 'Lv. ' + (levelNo || '-');
required.textContent =
formatCount(level.required_diamonds) + ' diamonds';
head.appendChild(title);
head.appendChild(required);
appendPolicyCell(
grid,
'Host salary',
policyAmount(level.host_salary_usd_minor)
);
appendPolicyCell(
grid,
'Host coin reward',
formatCount(level.host_coin_reward)
);
appendPolicyCell(
grid,
'Agency salary',
policyAmount(level.agency_salary_usd_minor)
);
appendPolicyCell(grid, 'Status', formatMode(level.status));
row.appendChild(head);
row.appendChild(grid);
list.appendChild(row);
}); });
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) { function openSheet(id) {
@ -620,9 +757,29 @@
document.body.classList.toggle('modal-open', hasOpen); document.body.classList.toggle('modal-open', hasOpen);
} }
function openPolicy() {
var modal = $('policyModal');
if (!modal) return;
modal.hidden = false;
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('modal-open');
renderPolicy();
if (!state.policy.loaded) loadPolicy();
}
function closePolicy() {
var modal = $('policyModal');
if (!modal) return;
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
}
function closeAllSheets() { function closeAllSheets() {
document document
.querySelectorAll('.sheet-modal, .message-modal, .confirm-modal') .querySelectorAll(
'.sheet-modal, .message-modal, .confirm-modal'
)
.forEach(function (sheet) { .forEach(function (sheet) {
sheet.hidden = true; sheet.hidden = true;
sheet.setAttribute('aria-hidden', 'true'); sheet.setAttribute('aria-hidden', 'true');
@ -633,11 +790,146 @@
function navigateToWithdrawExchange() { function navigateToWithdrawExchange() {
var params = new URLSearchParams(window.location.search); var params = new URLSearchParams(window.location.search);
if (!params.has('mode')) params.set('mode', 'transfer'); params.set('identity', 'AGENCY');
params.set('salaryType', AGENCY_SALARY_USD);
params.set('asset_type', AGENCY_SALARY_USD);
if (!params.has('mode')) params.set('mode', 'withdraw');
window.location.href = window.location.href =
'../withdraw-exchange/index.html?' + params.toString(); '../withdraw-exchange/index.html?' + params.toString();
} }
function safeRequest(factory) {
return factory().catch(function (error) {
if (window.console && window.console.warn) {
window.console.warn('[agency-center] api failed', error);
}
return null;
});
}
function setLoading(loading) {
state.loading = Boolean(loading);
state.profile.loading = Boolean(loading);
state.salary.loading = Boolean(loading);
state.policy = Object.assign({}, state.policy, {
loading: Boolean(loading),
});
render();
}
function applyAgencyCenterPayload(overview, hosts, applications, policy) {
if (overview) {
normalizeOverview(overview);
} else {
state.profile.loading = false;
state.salary.loading = false;
}
state.hosts = ((hosts && hosts.items) || []).map(normalizeHost);
state.messages = ((applications && applications.items) || []).map(
normalizeMessage
);
if (policy) {
applyPolicy(policy);
} else {
state.policy = {
loading: false,
loaded: true,
found: false,
policy: null,
};
}
}
function loadPolicy() {
var service = agencyAPI();
if (!service || !service.platformPolicy) {
state.policy = {
loading: false,
loaded: true,
found: false,
policy: null,
};
renderPolicy();
return Promise.resolve(false);
}
state.policy = Object.assign({}, state.policy, { loading: true });
renderPolicy();
return safeRequest(function () {
return service.platformPolicy();
}).then(function (payload) {
if (payload) {
applyPolicy(payload);
} else {
state.policy = {
loading: false,
loaded: true,
found: false,
policy: null,
};
}
renderPolicy();
return Boolean(payload);
});
}
function loadAgencyCenter() {
var service = agencyAPI();
setLoading(true);
if (
!service ||
!service.overview ||
!service.hosts ||
!service.applications ||
!service.platformPolicy
) {
markLoadedEmpty();
render();
toast(t('agency_center.api_not_ready', 'API module is not ready.'));
return Promise.resolve(false);
}
if (window.HyAppParams && window.HyAppParams.parse) {
window.HyAppParams.parse();
}
return Promise.all([
safeRequest(function () {
return service.overview();
}),
safeRequest(function () {
return service.hosts();
}),
safeRequest(function () {
return service.applications('pending');
}),
safeRequest(function () {
return service.platformPolicy();
}),
]).then(function (results) {
applyAgencyCenterPayload(
results[0],
results[1],
results[2],
results[3]
);
state.loading = false;
render();
if (
!results[0] &&
!results[1] &&
!results[2] &&
!results[3]
) {
toast(
t(
'agency_center.load_failed',
'Load failed. Try again later.'
)
);
return false;
}
return true;
});
}
function bindEvents() { function bindEvents() {
$('backButton').addEventListener('click', function () { $('backButton').addEventListener('click', function () {
if (window.HyAppBridge) window.HyAppBridge.back(); if (window.HyAppBridge) window.HyAppBridge.back();
@ -673,6 +965,11 @@
closeSheet(node.getAttribute('data-close-sheet')); closeSheet(node.getAttribute('data-close-sheet'));
}); });
}); });
document
.querySelectorAll('[data-close-policy]')
.forEach(function (button) {
button.addEventListener('click', closePolicy);
});
document.querySelectorAll('.menu-row').forEach(function (button) { document.querySelectorAll('.menu-row').forEach(function (button) {
button.addEventListener('click', function () { button.addEventListener('click', function () {
var action = button.getAttribute('data-action'); var action = button.getAttribute('data-action');
@ -683,24 +980,13 @@
openSheet('hostSheet'); openSheet('hostSheet');
return; return;
} }
if (action === 'team-bill') { if (action === 'policy') {
renderBillList(); openPolicy();
openSheet('billSheet');
return; return;
} }
if (action === 'invite') { if (action === 'invite') {
openSheet('inviteSheet'); 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) { document.addEventListener('keydown', function (event) {
@ -714,20 +1000,20 @@
renderHostCount(); renderHostCount();
renderUnread(); renderUnread();
renderHostList(); renderHostList();
renderBillList();
renderMessages(); renderMessages();
renderPolicy();
} }
function initPage() { function initPage() {
render(); render();
bindEvents(); bindEvents();
loadAgencyCenter(); loadAgencyCenter().finally(function () {
if (window.HyAppBridge) { if (window.HyAppBridge) {
window.HyAppBridge.ready({ window.HyAppBridge.ready({
page: 'agency-center', page: 'agency-center',
mock: state.isMock,
}); });
} }
});
} }
if (document.readyState === 'loading') { if (document.readyState === 'loading') {

View File

@ -434,6 +434,12 @@ input:focus-visible {
line-height: 1; line-height: 1;
} }
.salary-value #currentSalary.skeleton-line {
width: 160px;
height: 40px;
border-radius: 10px;
}
.chevron { .chevron {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -506,6 +512,12 @@ input:focus-visible {
white-space: nowrap; white-space: nowrap;
} }
.menu-count.skeleton-line {
width: 28px;
height: 16px;
border-radius: 7px;
}
.sheet-modal, .sheet-modal,
.message-modal, .message-modal,
.confirm-modal { .confirm-modal {
@ -661,7 +673,6 @@ input:focus-visible {
} }
.host-list, .host-list,
.bill-list,
.message-list, .message-list,
.invite-steps { .invite-steps {
display: grid; display: grid;
@ -673,7 +684,6 @@ input:focus-visible {
} }
.host-row, .host-row,
.bill-row,
.message-card, .message-card,
.invite-step { .invite-step {
border: 1px solid var(--line); border: 1px solid var(--line);
@ -681,46 +691,6 @@ input:focus-visible {
background: #fbfcfc; background: #fbfcfc;
} }
.bill-row {
display: grid;
gap: 10px;
padding: 12px;
}
.bill-month {
color: var(--text);
font-size: 15px;
font-weight: 950;
line-height: 1.2;
}
.bill-values {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.bill-cell span {
display: block;
overflow: hidden;
color: var(--muted);
font-size: 12px;
font-weight: 800;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.bill-cell strong {
display: block;
margin-top: 4px;
overflow-wrap: anywhere;
color: #292d33;
font-size: 16px;
font-weight: 950;
line-height: 1.2;
}
.host-search { .host-search {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) 82px; grid-template-columns: minmax(0, 1fr) 82px;
@ -776,6 +746,32 @@ input:focus-visible {
padding: 12px; padding: 12px;
} }
.host-row.is-skeleton {
pointer-events: none;
}
.host-row.is-skeleton .small-avatar {
background: transparent;
}
.host-row.is-skeleton .host-name.skeleton-line {
width: min(148px, 100%);
height: 18px;
border-radius: 7px;
}
.host-row.is-skeleton .host-meta.skeleton-line {
width: min(190px, 100%);
height: 14px;
border-radius: 7px;
}
.host-side-skeleton.skeleton-line {
width: 58px;
height: 32px;
border-radius: 8px;
}
.host-user, .host-user,
.message-user { .message-user {
display: flex; display: flex;
@ -908,6 +904,111 @@ input:focus-visible {
text-align: center; text-align: center;
} }
.policy-sheet-body {
min-height: 180px;
overflow-y: auto;
padding: 16px;
-webkit-overflow-scrolling: touch;
}
.policy-summary {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px;
border-radius: 8px;
background: var(--primary-soft);
}
.policy-summary strong {
overflow-wrap: anywhere;
color: var(--text);
font-size: 17px;
font-weight: 950;
line-height: 1.25;
}
.policy-summary span {
color: var(--muted);
font-size: 12px;
font-weight: 800;
line-height: 1.45;
}
.policy-empty {
padding: 22px 10px;
color: var(--muted);
text-align: center;
font-size: 14px;
font-weight: 850;
line-height: 1.45;
}
.policy-level-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 12px;
}
.policy-level-row {
padding: 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.policy-row-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.policy-level {
color: var(--text);
font-size: 16px;
font-weight: 950;
line-height: 1.25;
}
.policy-effective {
color: var(--muted);
text-align: right;
font-size: 12px;
font-weight: 800;
line-height: 1.25;
}
.policy-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 12px;
}
.policy-cell {
min-width: 0;
}
.policy-cell-label {
display: block;
color: var(--muted);
font-size: 11px;
font-weight: 800;
line-height: 1.2;
}
.policy-cell-value {
display: block;
margin-top: 4px;
overflow-wrap: anywhere;
color: #292d33;
font-size: 14px;
font-weight: 950;
line-height: 1.25;
}
.modal-open { .modal-open {
overflow: hidden; overflow: hidden;
} }
@ -976,7 +1077,7 @@ input:focus-visible {
margin-left: 14px; margin-left: 14px;
} }
.bill-values { .policy-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }