590 lines
19 KiB
JavaScript
590 lines
19 KiB
JavaScript
(function () {
|
|
var EXCHANGE_RATE = 80000;
|
|
var WITHDRAW_MIN_AMOUNT = 50;
|
|
var params = new URLSearchParams(window.location.search);
|
|
var currentMode = normalizeMode(params.get('mode')) || 'transfer';
|
|
var state = {
|
|
balance: 0,
|
|
paymentInfo: null,
|
|
selectedPayee: null,
|
|
isSearching: false,
|
|
isSaving: false,
|
|
isSubmitting: false,
|
|
historyItems: [],
|
|
};
|
|
var lastSearchTriggerAt = 0;
|
|
var autoSearchTimer = 0;
|
|
|
|
var mock = {
|
|
balance: 30650.75,
|
|
paymentInfo: null,
|
|
payees: {
|
|
163003: {
|
|
id: 'payee_163003',
|
|
name: 'Yumi Star Agency',
|
|
account: '163003',
|
|
avatar: '',
|
|
},
|
|
163000: {
|
|
id: 'payee_163000',
|
|
name: 'quick_1780052629537',
|
|
account: '163000',
|
|
avatar: '',
|
|
},
|
|
},
|
|
history: [
|
|
{
|
|
key: 'withdraw_exchange.history_host_salary',
|
|
title: 'Host salary',
|
|
amount: 18400.25,
|
|
balance: 30650.75,
|
|
type: 0,
|
|
date: '2026-06-01 12:10',
|
|
},
|
|
{
|
|
key: 'withdraw_exchange.history_exchange_to_golds',
|
|
title: 'Exchange to golds',
|
|
amount: 800,
|
|
balance: 12250.5,
|
|
type: 1,
|
|
date: '2026-05-30 20:42',
|
|
},
|
|
{
|
|
key: 'withdraw_exchange.history_gift_reward',
|
|
title: 'Gift reward',
|
|
amount: 8250.5,
|
|
balance: 13050.5,
|
|
type: 0,
|
|
date: '2026-05-28 18:30',
|
|
},
|
|
],
|
|
};
|
|
|
|
var inputs = {
|
|
withdraw: $('withdrawAmount'),
|
|
exchange: $('exchangeAmount'),
|
|
transfer: $('transferAmount'),
|
|
};
|
|
|
|
function $(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
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 normalizeMode(mode) {
|
|
var value = String(mode || '').trim();
|
|
return ['withdraw', 'exchange', 'transfer'].indexOf(value) >= 0
|
|
? value
|
|
: '';
|
|
}
|
|
|
|
function money(value) {
|
|
var number = Number(value || 0);
|
|
return number.toLocaleString('en-US', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
}
|
|
|
|
function moneyInput(value) {
|
|
var number = Number(value || 0);
|
|
return Number.isFinite(number) ? number.toFixed(2) : '0.00';
|
|
}
|
|
|
|
function amount() {
|
|
var input = inputs[currentMode];
|
|
return Number(input && input.value ? input.value : 0) || 0;
|
|
}
|
|
|
|
function delay(value) {
|
|
return new Promise(function (resolve) {
|
|
window.setTimeout(function () {
|
|
resolve(value);
|
|
}, 260);
|
|
});
|
|
}
|
|
|
|
function mockRequest(action, payload) {
|
|
if (action === 'balance') return delay(mock.balance);
|
|
if (action === 'payment') return delay(mock.paymentInfo);
|
|
if (action === 'history') return delay(mock.history.slice());
|
|
if (action === 'saveAddress') {
|
|
mock.paymentInfo = {
|
|
id: 'usdt_mock_1',
|
|
cardNo: payload.cardNo,
|
|
};
|
|
return delay(mock.paymentInfo);
|
|
}
|
|
if (action === 'searchPayee') {
|
|
var account = String(payload.account || '').trim();
|
|
var payee = mock.payees[account];
|
|
if (!payee && account && account !== '000000') {
|
|
payee = {
|
|
id: 'payee_' + account,
|
|
name: 'User ' + account,
|
|
nameKey: 'withdraw_exchange.mock_user_name',
|
|
account: account,
|
|
avatar: '',
|
|
};
|
|
}
|
|
return delay(payee || null);
|
|
}
|
|
if (action === 'submit') {
|
|
mock.balance = Math.max(0, mock.balance - payload.amount);
|
|
mock.history.unshift({
|
|
title: titleForMode(payload.mode),
|
|
amount: payload.amount,
|
|
balance: mock.balance,
|
|
type: 1,
|
|
date: new Date().toISOString().replace('T', ' ').slice(0, 16),
|
|
});
|
|
return delay({ ok: true });
|
|
}
|
|
return delay(null);
|
|
}
|
|
|
|
function titleForMode(mode) {
|
|
if (mode === 'withdraw')
|
|
return t('withdraw_exchange.withdraw', 'Withdraw');
|
|
if (mode === 'exchange')
|
|
return t('withdraw_exchange.exchange', 'Exchange');
|
|
return t('withdraw_exchange.transfer', 'Transfer');
|
|
}
|
|
|
|
function isValidTrc20(value) {
|
|
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(String(value || '').trim());
|
|
}
|
|
|
|
function maskAddress(value) {
|
|
var text = String(value || '').trim();
|
|
if (text.length <= 12) return text || '-';
|
|
return text.slice(0, 6) + '...' + text.slice(-6);
|
|
}
|
|
|
|
function activeSubmitLabel() {
|
|
if (currentMode === 'withdraw')
|
|
return t('withdraw_exchange.withdraw_now', 'Withdraw now');
|
|
if (currentMode === 'exchange')
|
|
return t('withdraw_exchange.exchange_now', 'Exchange now');
|
|
return t('withdraw_exchange.transfer_now', 'Transfer now');
|
|
}
|
|
|
|
function renderBalance() {
|
|
$('balanceAmount').textContent = money(state.balance);
|
|
Object.keys(inputs).forEach(function (key) {
|
|
if (inputs[key]) inputs[key].max = moneyInput(state.balance);
|
|
});
|
|
}
|
|
|
|
function renderMode() {
|
|
document.querySelectorAll('.mode-tab').forEach(function (tab) {
|
|
var active = tab.getAttribute('data-mode') === currentMode;
|
|
tab.classList.toggle('active', active);
|
|
tab.setAttribute('aria-selected', String(active));
|
|
});
|
|
document.querySelectorAll('.mode-panel').forEach(function (panel) {
|
|
panel.classList.toggle(
|
|
'active',
|
|
panel.getAttribute('data-panel') === currentMode
|
|
);
|
|
});
|
|
$('paymentSection').hidden = currentMode !== 'withdraw';
|
|
updateSubmit();
|
|
}
|
|
|
|
function renderPaymentInfo() {
|
|
var payment = state.paymentInfo;
|
|
$('emptyAddressState').hidden = Boolean(payment);
|
|
$('addressForm').hidden = Boolean(payment);
|
|
$('boundAddressCard').hidden = !payment;
|
|
if (payment)
|
|
$('boundAddress').textContent = maskAddress(payment.cardNo);
|
|
updateSubmit();
|
|
}
|
|
|
|
function renderPayee() {
|
|
var result = $('payeeResult');
|
|
result.textContent = '';
|
|
if (!state.selectedPayee) {
|
|
result.hidden = true;
|
|
updateSubmit();
|
|
return;
|
|
}
|
|
|
|
var payee = state.selectedPayee;
|
|
var payeeName = payee.nameKey
|
|
? t(payee.nameKey, payee.name || 'User {account}').replace(
|
|
'{account}',
|
|
payee.account
|
|
)
|
|
: payee.name;
|
|
var avatar = document.createElement('div');
|
|
var copy = document.createElement('div');
|
|
var name = document.createElement('div');
|
|
var id = document.createElement('div');
|
|
avatar.className = 'payee-avatar';
|
|
copy.className = 'payee-copy';
|
|
name.className = 'payee-name';
|
|
id.className = 'payee-id';
|
|
avatar.textContent = String(payeeName || 'U')
|
|
.charAt(0)
|
|
.toUpperCase();
|
|
name.textContent = payeeName || '-';
|
|
id.textContent =
|
|
t('withdraw_exchange.uid_prefix', 'UID:') + ' ' + payee.account;
|
|
copy.appendChild(name);
|
|
copy.appendChild(id);
|
|
result.appendChild(avatar);
|
|
result.appendChild(copy);
|
|
result.hidden = false;
|
|
updateSubmit();
|
|
}
|
|
|
|
function renderHistory() {
|
|
$('salaryHistoryTotal').textContent = '$' + money(state.balance);
|
|
var list = $('salaryHistoryList');
|
|
list.textContent = '';
|
|
if (!state.historyItems.length) {
|
|
var empty = document.createElement('div');
|
|
empty.className = 'salary-history-empty';
|
|
empty.textContent = t(
|
|
'withdraw_exchange.no_salary_history',
|
|
'No salary history'
|
|
);
|
|
list.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
state.historyItems.forEach(function (item) {
|
|
var row = document.createElement('article');
|
|
var main = document.createElement('div');
|
|
var title = document.createElement('div');
|
|
var meta = document.createElement('div');
|
|
var value = document.createElement('div');
|
|
var isOut = Number(item.type) === 1;
|
|
row.className = 'salary-history-item';
|
|
main.className = 'salary-history-item-main';
|
|
title.className = 'salary-history-item-title';
|
|
meta.className = 'salary-history-item-meta';
|
|
value.className =
|
|
'salary-history-amount' + (isOut ? ' is-out' : '');
|
|
title.textContent = item.key ? t(item.key, item.title) : item.title;
|
|
meta.textContent =
|
|
item.date +
|
|
' ' +
|
|
t('withdraw_exchange.balance', 'Balance') +
|
|
': $' +
|
|
money(item.balance);
|
|
value.textContent = (isOut ? '-' : '+') + '$' + money(item.amount);
|
|
main.appendChild(title);
|
|
main.appendChild(meta);
|
|
row.appendChild(main);
|
|
row.appendChild(value);
|
|
list.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function updateSubmit() {
|
|
var button = $('submitButton');
|
|
if (state.isSubmitting) {
|
|
button.textContent = t(
|
|
'withdraw_exchange.submitting',
|
|
'Submitting...'
|
|
);
|
|
button.disabled = true;
|
|
return;
|
|
}
|
|
|
|
var value = amount();
|
|
var disabled =
|
|
value <= 0 ||
|
|
value > state.balance ||
|
|
(currentMode === 'withdraw' &&
|
|
(!state.paymentInfo || value < WITHDRAW_MIN_AMOUNT)) ||
|
|
(currentMode === 'transfer' && !state.selectedPayee);
|
|
button.textContent = activeSubmitLabel();
|
|
button.disabled = disabled;
|
|
}
|
|
|
|
function normalizeAmount(event) {
|
|
var input = event.target;
|
|
var raw = String(input.value || '').replace(/[^\d.]/g, '');
|
|
var parts = raw.split('.');
|
|
var next =
|
|
parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : raw;
|
|
var value = Number(next);
|
|
if (Number.isFinite(value) && value > state.balance)
|
|
next = moneyInput(state.balance);
|
|
input.value = next;
|
|
updateSubmit();
|
|
}
|
|
|
|
function setMode(mode) {
|
|
currentMode = normalizeMode(mode) || 'transfer';
|
|
renderMode();
|
|
}
|
|
|
|
function openHistory() {
|
|
$('salaryHistoryModal').hidden = false;
|
|
document.body.classList.add('modal-open');
|
|
$('salaryHistoryList').textContent = t(
|
|
'withdraw_exchange.loading',
|
|
'Loading...'
|
|
);
|
|
mockRequest('history').then(function (items) {
|
|
state.historyItems = items || [];
|
|
renderHistory();
|
|
});
|
|
}
|
|
|
|
function closeHistory() {
|
|
$('salaryHistoryModal').hidden = true;
|
|
document.body.classList.remove('modal-open');
|
|
}
|
|
|
|
function saveAddress() {
|
|
var address = String($('trc20Address').value || '').trim();
|
|
if (!isValidTrc20(address)) {
|
|
toast(
|
|
t(
|
|
'withdraw_exchange.invalid_address',
|
|
'Please enter a valid TRC20 USDT address'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
state.isSaving = true;
|
|
$('saveAddressButton').disabled = true;
|
|
$('saveAddressButton').textContent = t(
|
|
'withdraw_exchange.saving',
|
|
'Saving...'
|
|
);
|
|
mockRequest('saveAddress', { cardNo: address }).then(
|
|
function (payment) {
|
|
state.paymentInfo = payment;
|
|
state.isSaving = false;
|
|
$('saveAddressButton').disabled = false;
|
|
$('saveAddressButton').textContent = t(
|
|
'withdraw_exchange.save_address',
|
|
'Save address'
|
|
);
|
|
renderPaymentInfo();
|
|
toast(t('withdraw_exchange.address_saved', 'Address saved'));
|
|
}
|
|
);
|
|
}
|
|
|
|
function searchPayee() {
|
|
var now = Date.now();
|
|
if (now - lastSearchTriggerAt < 240) return;
|
|
lastSearchTriggerAt = now;
|
|
|
|
var account = String($('transferAccount').value || '').trim();
|
|
if (!account) {
|
|
toast(
|
|
t(
|
|
'withdraw_exchange.missing_payee',
|
|
'Please search and select a user'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
state.selectedPayee = null;
|
|
renderPayee();
|
|
state.isSearching = true;
|
|
$('searchPayeeButton').disabled = true;
|
|
$('searchPayeeButton').textContent = t(
|
|
'withdraw_exchange.searching',
|
|
'Searching...'
|
|
);
|
|
mockRequest('searchPayee', { account: account }).then(function (payee) {
|
|
state.isSearching = false;
|
|
$('searchPayeeButton').disabled = false;
|
|
$('searchPayeeButton').textContent = t(
|
|
'withdraw_exchange.search',
|
|
'Search'
|
|
);
|
|
if (!payee) {
|
|
toast(t('withdraw_exchange.payee_not_found', 'User not found'));
|
|
return;
|
|
}
|
|
state.selectedPayee = payee;
|
|
renderPayee();
|
|
});
|
|
}
|
|
|
|
function validateSubmit() {
|
|
var value = amount();
|
|
if (value <= 0)
|
|
return t(
|
|
'withdraw_exchange.invalid_amount',
|
|
'Please enter a valid amount'
|
|
);
|
|
if (value > state.balance)
|
|
return t(
|
|
'withdraw_exchange.amount_exceeds_balance',
|
|
'Amount exceeds salary'
|
|
);
|
|
if (currentMode === 'withdraw' && !state.paymentInfo)
|
|
return t(
|
|
'withdraw_exchange.missing_payment_info',
|
|
'Please add a TRC20 USDT address'
|
|
);
|
|
if (currentMode === 'withdraw' && value < WITHDRAW_MIN_AMOUNT)
|
|
return t(
|
|
'withdraw_exchange.amount_too_low',
|
|
'Minimum withdrawal amount is 50 USD'
|
|
);
|
|
if (currentMode === 'transfer' && !state.selectedPayee)
|
|
return t(
|
|
'withdraw_exchange.missing_payee',
|
|
'Please search and select a user'
|
|
);
|
|
return '';
|
|
}
|
|
|
|
function submit() {
|
|
var error = validateSubmit();
|
|
if (error) {
|
|
toast(error);
|
|
return;
|
|
}
|
|
state.isSubmitting = true;
|
|
updateSubmit();
|
|
mockRequest('submit', {
|
|
mode: currentMode,
|
|
amount: amount(),
|
|
payee: state.selectedPayee,
|
|
paymentInfo: state.paymentInfo,
|
|
}).then(function () {
|
|
var input = inputs[currentMode];
|
|
if (input) input.value = '';
|
|
if (currentMode === 'transfer') {
|
|
state.selectedPayee = null;
|
|
$('transferAccount').value = '';
|
|
renderPayee();
|
|
}
|
|
state.balance = mock.balance;
|
|
state.isSubmitting = false;
|
|
renderBalance();
|
|
updateSubmit();
|
|
var key =
|
|
currentMode === 'withdraw'
|
|
? 'withdraw_exchange.withdraw_success'
|
|
: currentMode === 'exchange'
|
|
? 'withdraw_exchange.exchange_success'
|
|
: 'withdraw_exchange.transfer_success';
|
|
toast(t(key, 'Submitted'));
|
|
});
|
|
}
|
|
|
|
function goBack() {
|
|
if (window.history.length > 1) {
|
|
window.history.back();
|
|
return;
|
|
}
|
|
var nextParams = new URLSearchParams(window.location.search);
|
|
nextParams.delete('mode');
|
|
var query = nextParams.toString();
|
|
window.location.href =
|
|
'../host-center/index.html' + (query ? '?' + query : '');
|
|
}
|
|
|
|
function bindEvents() {
|
|
function bindTap(id, handler) {
|
|
var node = $(id);
|
|
var last = 0;
|
|
function run(event) {
|
|
var now = Date.now();
|
|
if (now - last < 220) return;
|
|
last = now;
|
|
if (event && event.type === 'pointerdown') {
|
|
event.preventDefault();
|
|
}
|
|
handler(event);
|
|
}
|
|
node.addEventListener('pointerdown', run);
|
|
node.addEventListener('click', run);
|
|
}
|
|
|
|
bindTap('backButton', goBack);
|
|
document.querySelectorAll('.mode-tab').forEach(function (tab) {
|
|
tab.addEventListener('click', function () {
|
|
setMode(tab.getAttribute('data-mode'));
|
|
});
|
|
});
|
|
Object.keys(inputs).forEach(function (key) {
|
|
if (inputs[key])
|
|
inputs[key].addEventListener('input', normalizeAmount);
|
|
});
|
|
bindTap('allButton', function () {
|
|
inputs.withdraw.value = moneyInput(state.balance);
|
|
updateSubmit();
|
|
});
|
|
$('transferAccount').addEventListener('input', function () {
|
|
state.selectedPayee = null;
|
|
renderPayee();
|
|
window.clearTimeout(autoSearchTimer);
|
|
if (String($('transferAccount').value || '').trim().length >= 3) {
|
|
autoSearchTimer = window.setTimeout(searchPayee, 260);
|
|
}
|
|
});
|
|
$('transferAccount').addEventListener('keydown', function (event) {
|
|
if (event.key === 'Enter') searchPayee();
|
|
});
|
|
bindTap('searchPayeeButton', searchPayee);
|
|
bindTap('saveAddressButton', saveAddress);
|
|
bindTap('submitButton', submit);
|
|
bindTap('salaryHistoryButton', openHistory);
|
|
bindTap('salaryHistoryBackdrop', closeHistory);
|
|
bindTap('salaryHistoryCloseButton', closeHistory);
|
|
}
|
|
|
|
function renderStaticDynamicText() {
|
|
$('exchangeRateText').textContent = t(
|
|
'withdraw_exchange.exchange_rate_value',
|
|
'1$ = {amount} golds'
|
|
).replace('{amount}', EXCHANGE_RATE.toLocaleString('en-US'));
|
|
if (!$('salaryHistoryModal').hidden) renderHistory();
|
|
updateSubmit();
|
|
renderPayee();
|
|
}
|
|
|
|
function init() {
|
|
bindEvents();
|
|
setMode(currentMode);
|
|
renderStaticDynamicText();
|
|
mockRequest('balance').then(function (balance) {
|
|
state.balance = balance;
|
|
renderBalance();
|
|
updateSubmit();
|
|
});
|
|
mockRequest('payment').then(function (payment) {
|
|
state.paymentInfo = payment;
|
|
renderPaymentInfo();
|
|
});
|
|
if (window.HyAppBridge) {
|
|
window.HyAppBridge.ready({
|
|
page: 'withdraw-exchange',
|
|
mock: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
window.addEventListener('hyapp:i18n-ready', renderStaticDynamicText);
|
|
})();
|