1115 lines
36 KiB
JavaScript
1115 lines
36 KiB
JavaScript
(function () {
|
|
var SELLER_ASSET_TYPE = 'COIN_SELLER_COIN';
|
|
var query = new URLSearchParams(window.location.search);
|
|
|
|
var mock = {
|
|
profile: {
|
|
name: 'CoinSeller Mina',
|
|
uid: '163009',
|
|
region: 'Saudi Arabia',
|
|
avatar: avatarData('C', '#dbc8ff', '#7d57c7'),
|
|
},
|
|
balance: {
|
|
coins: 1285000,
|
|
},
|
|
users: [
|
|
{
|
|
uid: '2001',
|
|
userId: '9002001',
|
|
name: 'Alice',
|
|
avatar: avatarData('A', '#dbc8ff', '#7d57c7'),
|
|
},
|
|
{
|
|
uid: '2002',
|
|
userId: '9002002',
|
|
name: 'Bob',
|
|
avatar: avatarData('B', '#f4ebff', '#7d57c7'),
|
|
},
|
|
{
|
|
uid: '2003',
|
|
userId: '9002003',
|
|
name: 'Nora',
|
|
avatar: avatarData('N', '#efe4ff', '#7d57c7'),
|
|
},
|
|
],
|
|
quickAmounts: [10000, 50000, 100000],
|
|
records: [
|
|
{
|
|
id: 'tx-001',
|
|
type: 'income',
|
|
name: 'Stock transfer',
|
|
uid: 'System',
|
|
avatar: avatarData('S', '#e7f7f3', '#31a88f'),
|
|
coins: 300000,
|
|
amount: 36,
|
|
currency: 'USD',
|
|
time: '2026-06-04 10:30',
|
|
},
|
|
{
|
|
id: 'tx-002',
|
|
type: 'expenditure',
|
|
name: 'Alice',
|
|
uid: '2001',
|
|
avatar: avatarData('A', '#dbc8ff', '#7d57c7'),
|
|
coins: 110000,
|
|
amount: 12.5,
|
|
currency: 'USD',
|
|
time: '2026-06-04 10:10',
|
|
},
|
|
{
|
|
id: 'tx-003',
|
|
type: 'expenditure',
|
|
name: 'Bob',
|
|
uid: '2002',
|
|
avatar: avatarData('B', '#f4ebff', '#7d57c7'),
|
|
coins: 88000,
|
|
amount: 9.25,
|
|
currency: 'USD',
|
|
time: '2026-06-04 09:20',
|
|
},
|
|
{
|
|
id: 'tx-004',
|
|
type: 'income',
|
|
name: 'Manager recharge',
|
|
uid: 'System',
|
|
avatar: avatarData('M', '#e7f7f3', '#31a88f'),
|
|
coins: 180000,
|
|
amount: 22,
|
|
currency: 'USD',
|
|
time: '2026-06-03 18:42',
|
|
},
|
|
],
|
|
};
|
|
|
|
var data = {
|
|
profile: emptyProfile(),
|
|
balance: { coins: 0 },
|
|
users: [],
|
|
quickAmounts: mock.quickAmounts.slice(),
|
|
records: [],
|
|
};
|
|
|
|
var state = {
|
|
target: null,
|
|
coinAmount: '',
|
|
historyTab: 'income',
|
|
historyQuery: '',
|
|
historyLimit: 3,
|
|
isMock: query.get('mock') === '1',
|
|
loading: query.get('mock') !== '1',
|
|
searching: false,
|
|
submitting: false,
|
|
};
|
|
|
|
function $(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
function t(key, fallback) {
|
|
return window.HyAppI18n && window.HyAppI18n.t
|
|
? window.HyAppI18n.t(key, fallback)
|
|
: fallback || key;
|
|
}
|
|
|
|
function avatarData(text, background, foreground) {
|
|
var value =
|
|
String(text || 'C')
|
|
.trim()
|
|
.charAt(0) || 'C';
|
|
var letter = value.replace(/[<>&"']/g, '');
|
|
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 emptyProfile() {
|
|
return {
|
|
name: '',
|
|
uid: '',
|
|
region: '',
|
|
avatar: '',
|
|
};
|
|
}
|
|
|
|
function initial(text) {
|
|
return String(text || '')
|
|
.trim()
|
|
.charAt(0)
|
|
.toUpperCase();
|
|
}
|
|
|
|
function count(value) {
|
|
return Number(value || 0).toLocaleString('en-US', {
|
|
maximumFractionDigits: 0,
|
|
});
|
|
}
|
|
|
|
function toNumber(value) {
|
|
var number = Number(value);
|
|
return Number.isFinite(number) ? number : 0;
|
|
}
|
|
|
|
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 replaceChildren(node, children) {
|
|
node.textContent = '';
|
|
children.forEach(function (child) {
|
|
node.appendChild(child);
|
|
});
|
|
}
|
|
|
|
function toast(message) {
|
|
if (!message) return;
|
|
if (window.HyAppToast && window.HyAppToast.show) {
|
|
window.HyAppToast.show(message);
|
|
return;
|
|
}
|
|
window.alert(message);
|
|
}
|
|
|
|
function setStatus(node, message, type) {
|
|
node.hidden = !message;
|
|
node.textContent = message || '';
|
|
node.className = 'form-status' + (type ? ' ' + type : '');
|
|
}
|
|
|
|
function setSkeleton(element, loading, text) {
|
|
if (!element) return;
|
|
element.classList.toggle('skeleton-line', !!loading);
|
|
if (loading) {
|
|
element.textContent = '';
|
|
return;
|
|
}
|
|
element.textContent = text || '';
|
|
}
|
|
|
|
function hasAPI() {
|
|
return (
|
|
window.HyAppAPI &&
|
|
window.HyAppAPI.user &&
|
|
window.HyAppAPI.wallet
|
|
);
|
|
}
|
|
|
|
function setImage(image, fallback, src, name) {
|
|
if (!image || !fallback) return;
|
|
fallback.textContent = initial(name) || 'C';
|
|
if (!src) {
|
|
image.hidden = true;
|
|
image.removeAttribute('src');
|
|
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 renderAvatar(container, item) {
|
|
container.textContent = '';
|
|
if (item.avatar) {
|
|
var image = document.createElement('img');
|
|
image.src = item.avatar;
|
|
image.alt = '';
|
|
image.addEventListener('error', function () {
|
|
container.textContent = initial(item.name || item.uid) || 'U';
|
|
});
|
|
container.appendChild(image);
|
|
return;
|
|
}
|
|
container.textContent = initial(item.name || item.uid) || 'U';
|
|
}
|
|
|
|
function normalizeProfile(payload) {
|
|
var source = unwrapData(payload) || {};
|
|
return {
|
|
name:
|
|
firstValue(source, [
|
|
'username',
|
|
'nickname',
|
|
'name',
|
|
'display_user_id',
|
|
'displayUserID',
|
|
'user_id',
|
|
'userId',
|
|
]) || '-',
|
|
uid:
|
|
firstValue(source, [
|
|
'display_user_id',
|
|
'displayUserID',
|
|
'displayUserId',
|
|
'user_id',
|
|
'userId',
|
|
]) || '-',
|
|
region:
|
|
firstValue(source, [
|
|
'country_display_name',
|
|
'countryDisplayName',
|
|
'country_name',
|
|
'countryName',
|
|
'region_name',
|
|
'regionName',
|
|
'country',
|
|
'region',
|
|
]) || '',
|
|
avatar:
|
|
firstValue(source, [
|
|
'avatar',
|
|
'avatar_url',
|
|
'avatarUrl',
|
|
'profile_picture_url',
|
|
'profilePictureUrl',
|
|
]) || '',
|
|
};
|
|
}
|
|
|
|
function normalizeBalance(payload) {
|
|
var source = unwrapData(payload) || {};
|
|
var balances = Array.isArray(source.balances)
|
|
? source.balances
|
|
: Array.isArray(source)
|
|
? source
|
|
: [];
|
|
var matched =
|
|
balances.find(function (item) {
|
|
return item && item.asset_type === SELLER_ASSET_TYPE;
|
|
}) ||
|
|
balances.find(function (item) {
|
|
return item && item.assetType === SELLER_ASSET_TYPE;
|
|
}) ||
|
|
balances[0] ||
|
|
{};
|
|
return {
|
|
coins: toNumber(
|
|
firstValue(matched, [
|
|
'available_amount',
|
|
'availableAmount',
|
|
'available',
|
|
'amount',
|
|
])
|
|
),
|
|
};
|
|
}
|
|
|
|
function timeLabelFromMS(value) {
|
|
var number = Number(value || 0);
|
|
if (!Number.isFinite(number) || number <= 0) return '';
|
|
var date = new Date(number);
|
|
if (Number.isNaN(date.getTime())) return '';
|
|
return (
|
|
date.getFullYear() +
|
|
'-' +
|
|
String(date.getMonth() + 1).padStart(2, '0') +
|
|
'-' +
|
|
String(date.getDate()).padStart(2, '0') +
|
|
' ' +
|
|
String(date.getHours()).padStart(2, '0') +
|
|
':' +
|
|
String(date.getMinutes()).padStart(2, '0')
|
|
);
|
|
}
|
|
|
|
function normalizeResolvedUser(payload, keyword) {
|
|
var source = unwrapData(payload) || {};
|
|
var identity = source.identity || source.user_identity || source;
|
|
var profile = source.profile || source.user || source;
|
|
var displayID =
|
|
firstValue(identity, [
|
|
'display_user_id',
|
|
'displayUserID',
|
|
'displayUserId',
|
|
'display_id',
|
|
'displayId',
|
|
]) ||
|
|
firstValue(profile, [
|
|
'display_user_id',
|
|
'displayUserID',
|
|
'displayUserId',
|
|
'display_id',
|
|
'displayId',
|
|
]) ||
|
|
String(keyword || '').trim();
|
|
var userID =
|
|
firstValue(identity, ['user_id', 'userId', 'id']) ||
|
|
firstValue(profile, ['user_id', 'userId', 'id']) ||
|
|
'';
|
|
return {
|
|
uid: displayID,
|
|
userId: userID,
|
|
name:
|
|
firstValue(profile, ['username', 'nickname', 'name']) ||
|
|
firstValue(identity, ['username', 'nickname', 'name']) ||
|
|
t('coinseller_center.user_name_template', 'User {id}').replace(
|
|
'{id}',
|
|
displayID
|
|
),
|
|
avatar:
|
|
firstValue(profile, [
|
|
'avatar',
|
|
'avatar_url',
|
|
'avatarUrl',
|
|
'profile_picture_url',
|
|
'profilePictureUrl',
|
|
]) ||
|
|
firstValue(identity, [
|
|
'avatar',
|
|
'avatar_url',
|
|
'avatarUrl',
|
|
'profile_picture_url',
|
|
'profilePictureUrl',
|
|
]) ||
|
|
'',
|
|
};
|
|
}
|
|
|
|
function normalizeTransactionRecord(item, index) {
|
|
var delta = toNumber(
|
|
firstValue(item, ['available_delta', 'availableDelta'])
|
|
);
|
|
var type = delta >= 0 ? 'income' : 'expenditure';
|
|
var counterpartyUserID = String(
|
|
firstValue(item, ['counterparty_user_id', 'counterpartyUserId']) || ''
|
|
);
|
|
var counterpartyDisplayID = String(
|
|
firstValue(item, [
|
|
'counterparty_display_user_id',
|
|
'counterpartyDisplayUserId',
|
|
'counterpartyDisplayUserID',
|
|
]) || ''
|
|
);
|
|
var counterpartyName = firstValue(item, [
|
|
'counterparty_username',
|
|
'counterpartyUsername',
|
|
'counterparty_name',
|
|
'counterpartyName',
|
|
]);
|
|
var counterpartyAvatar = firstValue(item, [
|
|
'counterparty_avatar',
|
|
'counterpartyAvatar',
|
|
]);
|
|
var counterpartyUID = counterpartyDisplayID || counterpartyUserID;
|
|
var name =
|
|
type === 'income'
|
|
? t('coinseller_center.stock_transfer', 'Stock transfer')
|
|
: counterpartyName ||
|
|
(counterpartyUID
|
|
? t(
|
|
'coinseller_center.user_name_template',
|
|
'User {id}'
|
|
).replace('{id}', counterpartyUID)
|
|
: t('coinseller_center.transfer_out', 'Transfer out'));
|
|
var uid =
|
|
counterpartyUID ||
|
|
(type === 'income' ? t('coinseller_center.system', 'System') : '-');
|
|
return {
|
|
id:
|
|
firstValue(item, ['transaction_id', 'transactionId']) ||
|
|
String(firstValue(item, ['entry_id', 'entryId']) || index),
|
|
type: type,
|
|
name: name,
|
|
uid: uid,
|
|
avatar: counterpartyAvatar || avatarData(
|
|
name,
|
|
type === 'income' ? '#e7f7f3' : '#dbc8ff',
|
|
type === 'income' ? '#31a88f' : '#7d57c7'
|
|
),
|
|
coins: Math.abs(delta),
|
|
amount: 0,
|
|
currency: '',
|
|
time: timeLabelFromMS(
|
|
firstValue(item, ['created_at_ms', 'createdAtMs'])
|
|
),
|
|
};
|
|
}
|
|
|
|
function normalizeTransactions(payload) {
|
|
var source = unwrapData(payload) || {};
|
|
var items = Array.isArray(source.items)
|
|
? source.items
|
|
: Array.isArray(source.transactions)
|
|
? source.transactions
|
|
: Array.isArray(source)
|
|
? source
|
|
: [];
|
|
return items.map(function (item, index) {
|
|
return normalizeTransactionRecord(item || {}, index);
|
|
});
|
|
}
|
|
|
|
function applyMockData() {
|
|
data.profile = Object.assign({}, mock.profile);
|
|
data.balance = Object.assign({}, mock.balance);
|
|
data.users = mock.users.slice();
|
|
data.quickAmounts = mock.quickAmounts.slice();
|
|
data.records = mock.records.slice();
|
|
}
|
|
|
|
function applyEmptyData() {
|
|
data.profile = emptyProfile();
|
|
data.balance = { coins: 0 };
|
|
data.users = [];
|
|
data.quickAmounts = mock.quickAmounts.slice();
|
|
data.records = [];
|
|
}
|
|
|
|
function loadRealData() {
|
|
if (!hasAPI()) {
|
|
toast(t('coinseller_center.load_failed', 'Load failed. Try again later.'));
|
|
return Promise.resolve();
|
|
}
|
|
state.loading = true;
|
|
render();
|
|
return Promise.all([
|
|
window.HyAppAPI.user.me(),
|
|
window.HyAppAPI.wallet.balances(SELLER_ASSET_TYPE),
|
|
window.HyAppAPI.wallet.transactions
|
|
? window.HyAppAPI.wallet
|
|
.transactions({
|
|
asset_type: SELLER_ASSET_TYPE,
|
|
page: 1,
|
|
page_size: 20,
|
|
})
|
|
.catch(function (error) {
|
|
if (window.console && window.console.warn) {
|
|
window.console.warn(
|
|
'[coinseller-center] records api failed',
|
|
error
|
|
);
|
|
}
|
|
return null;
|
|
})
|
|
: Promise.resolve(null),
|
|
])
|
|
.then(function (results) {
|
|
data.profile = normalizeProfile(results[0]);
|
|
data.balance = normalizeBalance(results[1]);
|
|
data.records = normalizeTransactions(results[2]);
|
|
})
|
|
.catch(function (error) {
|
|
if (window.console && window.console.warn) {
|
|
window.console.warn('[coinseller-center] api failed', error);
|
|
}
|
|
toast(
|
|
t(
|
|
'coinseller_center.load_failed',
|
|
'Load failed. Try again later.'
|
|
)
|
|
);
|
|
})
|
|
.finally(function () {
|
|
state.loading = false;
|
|
render();
|
|
});
|
|
}
|
|
|
|
function renderProfile() {
|
|
var image = $('profileAvatar');
|
|
var fallback = $('profileAvatarFallback');
|
|
var profile = data.profile;
|
|
var loading = !!state.loading;
|
|
|
|
setSkeleton($('profileName'), loading, profile.name || '');
|
|
setSkeleton(
|
|
$('profileUID'),
|
|
loading,
|
|
profile.uid
|
|
? t('coinseller_center.uid_prefix', 'UID: {id}').replace(
|
|
'{id}',
|
|
profile.uid
|
|
)
|
|
: ''
|
|
);
|
|
fallback.classList.toggle('avatar-skeleton', loading);
|
|
if (loading) {
|
|
image.hidden = true;
|
|
image.removeAttribute('src');
|
|
fallback.hidden = false;
|
|
fallback.textContent = '';
|
|
return;
|
|
}
|
|
|
|
setImage(image, fallback, profile.avatar, profile.name || profile.uid);
|
|
}
|
|
|
|
function renderBalance() {
|
|
setSkeleton($('coinBalance'), state.loading, count(data.balance.coins));
|
|
var coinIcon = document.querySelector('.balance-value .coin-icon');
|
|
if (coinIcon) {
|
|
coinIcon.classList.toggle('skeleton-coin', !!state.loading);
|
|
}
|
|
}
|
|
|
|
function renderQuickAmounts() {
|
|
replaceChildren(
|
|
$('quickAmounts'),
|
|
data.quickAmounts.map(function (amount) {
|
|
var button = document.createElement('button');
|
|
button.className =
|
|
'quick-amount' +
|
|
(String(amount) === state.coinAmount ? ' is-active' : '');
|
|
button.type = 'button';
|
|
button.textContent = count(amount);
|
|
button.addEventListener('click', function () {
|
|
state.coinAmount = String(amount);
|
|
$('coinAmountInput').value = state.coinAmount;
|
|
setStatus($('rechargeStatus'), '', '');
|
|
renderRechargeState();
|
|
});
|
|
return button;
|
|
})
|
|
);
|
|
}
|
|
|
|
function renderTarget() {
|
|
var card = $('targetCard');
|
|
if (!state.target) {
|
|
card.hidden = true;
|
|
$('rechargeTargetName').textContent = '-';
|
|
return;
|
|
}
|
|
card.hidden = false;
|
|
renderAvatar($('targetAvatar'), state.target);
|
|
$('targetName').textContent = state.target.name;
|
|
$('targetUID').textContent = t(
|
|
'coinseller_center.uid_prefix',
|
|
'UID: {id}'
|
|
).replace('{id}', state.target.uid);
|
|
$('rechargeTargetName').textContent =
|
|
t('coinseller_center.target_user', 'Target user') +
|
|
': ' +
|
|
state.target.name;
|
|
}
|
|
|
|
function parsedCoinAmount() {
|
|
var input = $('coinAmountInput');
|
|
if (input) {
|
|
var sanitized = String(input.value || '').replace(/[^\d]/g, '');
|
|
if (input.value !== sanitized) input.value = sanitized;
|
|
state.coinAmount = sanitized;
|
|
}
|
|
return Number(state.coinAmount || 0);
|
|
}
|
|
|
|
function renderRechargeState() {
|
|
var amount = parsedCoinAmount();
|
|
var disabled =
|
|
state.loading ||
|
|
state.submitting ||
|
|
state.searching ||
|
|
!state.target ||
|
|
amount <= 0;
|
|
$('rechargeButton').disabled = disabled;
|
|
$('searchButton').disabled = state.searching;
|
|
renderQuickAmounts();
|
|
}
|
|
|
|
function recordMatchesQuery(record) {
|
|
if (!state.historyQuery) return true;
|
|
var keyword = state.historyQuery.toLowerCase();
|
|
return (
|
|
String(record.name || '')
|
|
.toLowerCase()
|
|
.indexOf(keyword) >= 0 ||
|
|
String(record.uid || '')
|
|
.toLowerCase()
|
|
.indexOf(keyword) >= 0
|
|
);
|
|
}
|
|
|
|
function visibleHistoryRecords() {
|
|
return data.records
|
|
.filter(function (record) {
|
|
return (
|
|
record.type === state.historyTab && recordMatchesQuery(record)
|
|
);
|
|
})
|
|
.slice(0, state.historyLimit);
|
|
}
|
|
|
|
function createRecordRow(record) {
|
|
var row = document.createElement('article');
|
|
var avatar = document.createElement('div');
|
|
var main = document.createElement('div');
|
|
var title = document.createElement('div');
|
|
var meta = document.createElement('div');
|
|
var side = document.createElement('div');
|
|
var amount = document.createElement('strong');
|
|
var time = document.createElement('span');
|
|
|
|
row.className = 'record-row';
|
|
avatar.className = 'record-avatar';
|
|
main.className = 'record-main';
|
|
title.className = 'record-title';
|
|
meta.className = 'record-meta';
|
|
side.className =
|
|
'record-side' + (record.type === 'income' ? ' is-income' : '');
|
|
|
|
renderAvatar(avatar, record);
|
|
title.textContent = record.name || '-';
|
|
meta.textContent = t('coinseller_center.uid_prefix', 'UID: {id}').replace(
|
|
'{id}',
|
|
record.uid || '-'
|
|
);
|
|
amount.textContent =
|
|
(record.type === 'income' ? '+' : '-') +
|
|
count(record.coins) +
|
|
' ' +
|
|
t('coinseller_center.coins_lower', 'coins');
|
|
time.textContent = record.time || '';
|
|
|
|
main.appendChild(title);
|
|
main.appendChild(meta);
|
|
side.appendChild(amount);
|
|
side.appendChild(time);
|
|
row.appendChild(avatar);
|
|
row.appendChild(main);
|
|
row.appendChild(side);
|
|
return row;
|
|
}
|
|
|
|
function renderRecent() {
|
|
replaceChildren(
|
|
$('recentList'),
|
|
data.records.slice(0, 3).map(function (record) {
|
|
return createRecordRow(record);
|
|
})
|
|
);
|
|
}
|
|
|
|
function renderHistory() {
|
|
document.querySelectorAll('[data-history-tab]').forEach(function (tab) {
|
|
var active = tab.getAttribute('data-history-tab') === state.historyTab;
|
|
tab.classList.toggle('is-active', active);
|
|
tab.setAttribute('aria-selected', String(active));
|
|
});
|
|
|
|
var all = data.records.filter(function (record) {
|
|
return record.type === state.historyTab && recordMatchesQuery(record);
|
|
});
|
|
var records = visibleHistoryRecords();
|
|
$('loadMoreButton').hidden = records.length >= all.length;
|
|
setStatus(
|
|
$('historyStatus'),
|
|
all.length
|
|
? ''
|
|
: t(
|
|
'coinseller_center.no_transaction_records',
|
|
'No transaction records'
|
|
),
|
|
all.length ? '' : 'warning'
|
|
);
|
|
replaceChildren(
|
|
$('historyList'),
|
|
records.map(function (record) {
|
|
return createRecordRow(record);
|
|
})
|
|
);
|
|
}
|
|
|
|
function findMockUser(keyword) {
|
|
var normalized = String(keyword || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
return data.users.find(function (user) {
|
|
return (
|
|
String(user.uid || '')
|
|
.toLowerCase()
|
|
.indexOf(normalized) >= 0 ||
|
|
String(user.name || '')
|
|
.toLowerCase()
|
|
.indexOf(normalized) >= 0
|
|
);
|
|
});
|
|
}
|
|
|
|
function findUser(keyword) {
|
|
if (state.isMock) {
|
|
return Promise.resolve(findMockUser(keyword) || null);
|
|
}
|
|
if (!hasAPI() || !window.HyAppAPI.user.resolveDisplayUserID) {
|
|
return Promise.reject(new Error('api_not_ready'));
|
|
}
|
|
return window.HyAppAPI.user
|
|
.resolveDisplayUserID(keyword)
|
|
.then(function (payload) {
|
|
return normalizeResolvedUser(payload, keyword);
|
|
});
|
|
}
|
|
|
|
function handleSearch(value) {
|
|
state.searching = true;
|
|
setStatus(
|
|
$('searchStatus'),
|
|
t('coinseller_center.loading', 'Loading...'),
|
|
''
|
|
);
|
|
renderRechargeState();
|
|
findUser(value)
|
|
.then(function (user) {
|
|
if (!user) {
|
|
state.target = null;
|
|
renderTarget();
|
|
renderRechargeState();
|
|
setStatus(
|
|
$('searchStatus'),
|
|
t('coinseller_center.user_not_found', 'User not found.'),
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
state.target = user;
|
|
renderTarget();
|
|
renderRechargeState();
|
|
setStatus($('searchStatus'), '', '');
|
|
})
|
|
.catch(function (error) {
|
|
if (window.console && window.console.warn) {
|
|
window.console.warn('[coinseller-center] user search failed', error);
|
|
}
|
|
state.target = null;
|
|
renderTarget();
|
|
renderRechargeState();
|
|
setStatus(
|
|
$('searchStatus'),
|
|
t('coinseller_center.user_not_found', 'User not found.'),
|
|
'warning'
|
|
);
|
|
})
|
|
.finally(function () {
|
|
state.searching = false;
|
|
renderRechargeState();
|
|
});
|
|
}
|
|
|
|
function commandID() {
|
|
return [
|
|
'coin-seller-transfer',
|
|
Date.now(),
|
|
Math.random().toString(36).slice(2, 8),
|
|
].join('-');
|
|
}
|
|
|
|
function buildTransferPayload(amount) {
|
|
return {
|
|
command_id: commandID(),
|
|
target_display_user_id: String(state.target.uid || '').trim(),
|
|
amount: amount,
|
|
reason: 'player recharge',
|
|
};
|
|
}
|
|
|
|
function appendTransferRecord(amount, response) {
|
|
var payload = unwrapData(response) || {};
|
|
var coins = toNumber(
|
|
firstValue(payload, [
|
|
'amount',
|
|
'coin_amount',
|
|
'coinAmount',
|
|
])
|
|
);
|
|
data.records.unshift({
|
|
id:
|
|
firstValue(payload, ['transaction_id', 'transactionId']) ||
|
|
'tx-' + Date.now(),
|
|
type: 'expenditure',
|
|
name: state.target.name,
|
|
uid: state.target.uid,
|
|
avatar: state.target.avatar,
|
|
coins: coins || amount,
|
|
amount: 0,
|
|
currency: '',
|
|
time: currentTimeLabel(),
|
|
});
|
|
}
|
|
|
|
function rechargeNow() {
|
|
var amount = parsedCoinAmount();
|
|
if (!state.target) {
|
|
setStatus(
|
|
$('rechargeStatus'),
|
|
t('coinseller_center.select_user_first', 'Select a user first.'),
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
if (amount <= 0) {
|
|
setStatus(
|
|
$('rechargeStatus'),
|
|
t(
|
|
'coinseller_center.valid_amount_required',
|
|
'Please enter a valid amount.'
|
|
),
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
if (amount > data.balance.coins) {
|
|
setStatus(
|
|
$('rechargeStatus'),
|
|
t('coinseller_center.amount_exceeds_stock', 'Amount exceeds stock.'),
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
if (state.isMock) {
|
|
data.balance.coins -= amount;
|
|
appendTransferRecord(amount, {});
|
|
finishRecharge();
|
|
return;
|
|
}
|
|
if (!hasAPI() || !window.HyAppAPI.wallet.transferCoinFromSeller) {
|
|
setStatus($('rechargeStatus'), '', '');
|
|
toast(t('coinseller_center.transfer_failed', 'Transfer failed.'));
|
|
return;
|
|
}
|
|
|
|
state.submitting = true;
|
|
setStatus(
|
|
$('rechargeStatus'),
|
|
t('coinseller_center.loading', 'Loading...'),
|
|
''
|
|
);
|
|
renderRechargeState();
|
|
window.HyAppAPI.wallet
|
|
.transferCoinFromSeller(buildTransferPayload(amount))
|
|
.then(function (response) {
|
|
var payload = unwrapData(response) || {};
|
|
var balanceAfter = toNumber(
|
|
firstValue(payload, [
|
|
'seller_balance_after',
|
|
'sellerBalanceAfter',
|
|
])
|
|
);
|
|
if (balanceAfter > 0 || payload.seller_balance_after === 0) {
|
|
data.balance.coins = balanceAfter;
|
|
} else {
|
|
data.balance.coins -= amount;
|
|
}
|
|
appendTransferRecord(amount, response);
|
|
finishRecharge();
|
|
})
|
|
.catch(function (error) {
|
|
if (window.console && window.console.warn) {
|
|
window.console.warn('[coinseller-center] transfer failed', error);
|
|
}
|
|
setStatus($('rechargeStatus'), '', '');
|
|
toast(
|
|
error && error.message
|
|
? error.message
|
|
: t('coinseller_center.transfer_failed', 'Transfer failed.')
|
|
);
|
|
})
|
|
.finally(function () {
|
|
state.submitting = false;
|
|
renderRechargeState();
|
|
});
|
|
}
|
|
|
|
function finishRecharge() {
|
|
state.coinAmount = '';
|
|
$('coinAmountInput').value = '';
|
|
setStatus(
|
|
$('rechargeStatus'),
|
|
t('coinseller_center.recharge_success', 'Recharge success.'),
|
|
'success'
|
|
);
|
|
renderBalance();
|
|
renderRecent();
|
|
renderRechargeState();
|
|
if (!$('historyModal').hidden) renderHistory();
|
|
closeModal('rechargeModal');
|
|
toast(t('coinseller_center.recharge_success', 'Recharge success.'));
|
|
}
|
|
|
|
function currentTimeLabel() {
|
|
var now = new Date();
|
|
return (
|
|
now.getFullYear() +
|
|
'-' +
|
|
String(now.getMonth() + 1).padStart(2, '0') +
|
|
'-' +
|
|
String(now.getDate()).padStart(2, '0') +
|
|
' ' +
|
|
String(now.getHours()).padStart(2, '0') +
|
|
':' +
|
|
String(now.getMinutes()).padStart(2, '0')
|
|
);
|
|
}
|
|
|
|
function openModal(id) {
|
|
var modal = $(id);
|
|
if (!modal) return;
|
|
modal.hidden = false;
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
document.body.classList.add('modal-open');
|
|
}
|
|
|
|
function closeModal(id) {
|
|
var modal = $(id);
|
|
if (!modal) return;
|
|
modal.hidden = true;
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
var hasOpen = Array.prototype.some.call(
|
|
document.querySelectorAll('.sheet-modal'),
|
|
function (node) {
|
|
return !node.hidden;
|
|
}
|
|
);
|
|
document.body.classList.toggle('modal-open', hasOpen);
|
|
}
|
|
|
|
function bindEvents() {
|
|
$('backButton').addEventListener('click', function () {
|
|
if (window.HyAppBridge) window.HyAppBridge.back();
|
|
});
|
|
$('historyButton').addEventListener('click', function () {
|
|
state.historyLimit = 3;
|
|
renderHistory();
|
|
openModal('historyModal');
|
|
});
|
|
$('viewAllHistoryButton').addEventListener('click', function () {
|
|
state.historyLimit = 3;
|
|
renderHistory();
|
|
openModal('historyModal');
|
|
});
|
|
$('helpButton').addEventListener('click', function () {
|
|
openModal('helpModal');
|
|
});
|
|
document.querySelectorAll('[data-close-modal]').forEach(function (node) {
|
|
node.addEventListener('click', function () {
|
|
closeModal(node.getAttribute('data-close-modal'));
|
|
});
|
|
});
|
|
$('searchForm').addEventListener('submit', function (event) {
|
|
event.preventDefault();
|
|
var value = $('userSearchInput').value.trim();
|
|
if (!value) {
|
|
setStatus(
|
|
$('searchStatus'),
|
|
t('coinseller_center.enter_user_id', 'Enter user ID'),
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
handleSearch(value);
|
|
});
|
|
$('openRechargeButton').addEventListener('click', function () {
|
|
if (!state.target) return;
|
|
state.coinAmount = '';
|
|
$('coinAmountInput').value = '';
|
|
setStatus($('rechargeStatus'), '', '');
|
|
renderRechargeState();
|
|
openModal('rechargeModal');
|
|
window.setTimeout(function () {
|
|
$('coinAmountInput').focus();
|
|
}, 80);
|
|
});
|
|
$('coinAmountInput').addEventListener('input', function () {
|
|
state.coinAmount = $('coinAmountInput').value.replace(/[^\d]/g, '');
|
|
$('coinAmountInput').value = state.coinAmount;
|
|
setStatus($('rechargeStatus'), '', '');
|
|
renderRechargeState();
|
|
});
|
|
$('rechargeForm').addEventListener('submit', function (event) {
|
|
event.preventDefault();
|
|
rechargeNow();
|
|
});
|
|
document.querySelectorAll('[data-history-tab]').forEach(function (tab) {
|
|
tab.addEventListener('click', function () {
|
|
state.historyTab = tab.getAttribute('data-history-tab');
|
|
state.historyLimit = 3;
|
|
renderHistory();
|
|
});
|
|
});
|
|
$('historySearchForm').addEventListener('submit', function (event) {
|
|
event.preventDefault();
|
|
state.historyQuery = $('historySearchInput').value.trim();
|
|
state.historyLimit = 3;
|
|
renderHistory();
|
|
});
|
|
$('historyClearButton').addEventListener('click', function () {
|
|
state.historyQuery = '';
|
|
$('historySearchInput').value = '';
|
|
state.historyLimit = 3;
|
|
renderHistory();
|
|
});
|
|
$('loadMoreButton').addEventListener('click', function () {
|
|
state.historyLimit += 3;
|
|
renderHistory();
|
|
});
|
|
document.addEventListener('keydown', function (event) {
|
|
if (event.key !== 'Escape') return;
|
|
closeModal('historyModal');
|
|
closeModal('helpModal');
|
|
});
|
|
}
|
|
|
|
function render() {
|
|
renderProfile();
|
|
renderBalance();
|
|
renderQuickAmounts();
|
|
renderTarget();
|
|
renderRechargeState();
|
|
renderRecent();
|
|
}
|
|
|
|
function init() {
|
|
if (state.isMock) {
|
|
applyMockData();
|
|
} else {
|
|
applyEmptyData();
|
|
}
|
|
render();
|
|
bindEvents();
|
|
if (window.HyAppBridge) {
|
|
window.HyAppBridge.ready({
|
|
page: 'coinseller-center',
|
|
mock: state.isMock,
|
|
});
|
|
}
|
|
if (!state.isMock) loadRealData();
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
window.addEventListener('hyapp:i18n-ready', function () {
|
|
render();
|
|
if (!$('historyModal').hidden) renderHistory();
|
|
});
|
|
})();
|