(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 = '' + '' + '' + letter + '' + ''; 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(); }); })();