(function () {
'use strict';
var page = document.getElementById('coinSellerPage');
var region = document.getElementById('coinSellerRegion');
var loadingView = document.getElementById('sellerLoading');
var emptyView = document.getElementById('sellerEmpty');
var errorView = document.getElementById('sellerError');
var retryButton = document.getElementById('sellerRetry');
var skeletonList = document.getElementById('sellerSkeletonList');
var sellerList = document.getElementById('sellerList');
var sellerTemplate = document.getElementById('sellerCardTemplate');
var params =
(window.HyAppParams &&
window.HyAppParams.current &&
window.HyAppParams.current.raw) ||
new URLSearchParams(window.location.search);
var requestSequence = 0;
var currentItems = [];
var currentView = 'loading';
if (!page || !region || !sellerList || !sellerTemplate) return;
function t(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback;
}
function format(message, values) {
return String(message || '').replace(
/\{([a-zA-Z0-9_]+)\}/g,
function (_, key) {
return Object.prototype.hasOwnProperty.call(values || {}, key)
? String(values[key])
: '';
}
);
}
function stringValue(value) {
return value === undefined || value === null
? ''
: String(value).trim();
}
function safeMediaURL(value) {
var raw = stringValue(value);
if (!raw) return '';
try {
var parsed = new URL(raw, window.location.href);
return parsed.protocol === 'https:' || parsed.protocol === 'http:'
? parsed.href
: '';
} catch (_) {
return '';
}
}
function safeWhatsAppURL(value) {
var raw = stringValue(value);
if (!raw) return '';
try {
var parsed = new URL(raw, window.location.href);
if (parsed.protocol === 'whatsapp:') return parsed.href;
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
return '';
}
var host = parsed.hostname.toLowerCase();
return host === 'wa.me' || host === 'api.whatsapp.com'
? parsed.href
: '';
} catch (_) {
return '';
}
}
function isVisualFixture() {
return params.get('mock') === '1';
}
function visualFixturePayload() {
var requestedCount = Number(params.get('mock_count') || 5);
var count = Number.isFinite(requestedCount)
? Math.max(0, Math.min(20, Math.floor(requestedCount)))
: 5;
var avatar = safeMediaURL(params.get('mock_avatar'));
var contactEnabled = params.get('mock_contact') !== '0';
var items = [];
// 该分支只服务显式 mock=1 的设计验收。生产请求失败时绝不进入这里,
// 否则示例币商会掩盖鉴权、区域或上游故障并被用户误认为真实联系人。
for (var index = 0; index < count; index += 1) {
items.push({
user_id: String(8800000 + index),
display_user_id: String(123456789 + index),
username: params.get('mock_name') || 'Namenamenamenamename',
avatar: avatar,
country_code: 'AE',
country_name: 'United Arab Emirates',
country_display_name: 'United Arab Emirates',
whatsapp_phone: contactEnabled ? '+971500000000' : '',
whatsapp_url: contactEnabled
? 'https://wa.me/971500000000'
: '',
});
}
return { items: items, total: items.length };
}
function normalizePayload(payload) {
if (!payload || !Array.isArray(payload.items)) {
throw new Error('coin_seller_response_invalid');
}
return payload.items.map(function (item) {
var source = item && typeof item === 'object' ? item : {};
return {
userId: stringValue(source.user_id),
displayUserId: stringValue(source.display_user_id),
username: stringValue(source.username),
avatar: stringValue(source.avatar),
countryCode: stringValue(source.country_code).toUpperCase(),
countryName: stringValue(
source.country_display_name || source.country_name
),
whatsappPhone: stringValue(source.whatsapp_phone),
whatsappURL: stringValue(source.whatsapp_url),
};
});
}
function sellerName(item) {
return (
item.username ||
item.displayUserId ||
item.userId ||
t('coin_seller_fami.unknown_seller', 'Seller')
);
}
function firstVisibleCharacter(value) {
var text = stringValue(value);
if (!text) return '';
if (window.Intl && window.Intl.Segmenter) {
var segments = new window.Intl.Segmenter(undefined, {
granularity: 'grapheme',
}).segment(text);
var first = segments[Symbol.iterator]().next();
return first.done ? '' : first.value.segment;
}
return Array.from(text)[0] || '';
}
function flagURL(countryCode) {
var code = stringValue(countryCode).toUpperCase();
if (!/^[A-Z]{2}$/.test(code)) return '';
if (code === 'AE') return './assets/fami/layers/flag-ae.webp';
// Figma 只提供 AE 叶子素材;其他国家必须按接口 country_code 请求真实国旗,
// 不能错误复用 AE 图。失败时保留 ISO code 作为有意义的单卡降级。
return 'https://flagcdn.com/w80/' + code.toLowerCase() + '.png';
}
function bindAvatar(card, item, name) {
var image = card.querySelector('.seller-avatar');
var fallback = card.querySelector('.seller-avatar-fallback');
var source = safeMediaURL(item.avatar);
fallback.textContent = firstVisibleCharacter(name);
image.alt = format(
t('coin_seller_fami.avatar_label', '{name} avatar'),
{ name: name }
);
image.addEventListener(
'load',
function () {
image.classList.add('is-loaded');
},
{ once: true }
);
image.addEventListener(
'error',
function () {
image.classList.remove('is-loaded');
image.removeAttribute('src');
},
{ once: true }
);
if (source) image.src = source;
}
function bindFlag(card, item) {
var image = card.querySelector('.seller-flag');
var fallback = card.querySelector('.seller-flag-code');
var code = item.countryCode;
var source = flagURL(code);
var country = item.countryName || code;
fallback.textContent = code;
image.alt = country
? format(t('coin_seller_fami.country_flag', '{country} flag'), {
country: country,
})
: '';
image.addEventListener(
'load',
function () {
image.classList.add('is-loaded');
},
{ once: true }
);
image.addEventListener(
'error',
function () {
image.classList.remove('is-loaded');
image.removeAttribute('src');
},
{ once: true }
);
if (source) image.src = source;
}
function bindContact(card, item, name) {
var contact = card.querySelector('.seller-contact');
var label = contact.querySelector('span');
var target = safeWhatsAppURL(item.whatsappURL);
var contactText = t('coin_seller_fami.contact', 'Contact');
label.textContent = contactText;
if (target) {
contact.href = target;
contact.target = '_blank';
contact.removeAttribute('aria-disabled');
contact.removeAttribute('tabindex');
contact.setAttribute(
'aria-label',
format(t('coin_seller_fami.contact_seller', 'Contact {name}'), {
name: name,
})
);
return;
}
// 空联系方式来自服务端的稳定 schema。控件必须真实禁用,而不是仍然
// 导航到空页或由客户端根据电话号码猜测一个 WhatsApp 地址。
contact.removeAttribute('href');
contact.removeAttribute('target');
contact.setAttribute('aria-disabled', 'true');
contact.setAttribute('tabindex', '-1');
contact.setAttribute(
'aria-label',
t('coin_seller_fami.contact_unavailable', 'Contact unavailable')
);
contact.addEventListener('click', function (event) {
event.preventDefault();
});
}
function createSellerCard(item, index) {
var fragment = sellerTemplate.content.cloneNode(true);
var card = fragment.querySelector('.seller-card');
var name = sellerName(item);
var visibleID = item.displayUserId || item.userId;
card.dataset.userId = item.userId;
card.dataset.index = String(index);
card.querySelector('.seller-name').textContent = name;
card.querySelector('.seller-display-id').textContent = format(
t('coin_seller_fami.seller_id', 'ID: {id}'),
{ id: visibleID }
);
bindAvatar(card, item, name);
bindFlag(card, item);
bindContact(card, item, name);
return fragment;
}
function renderSellers(items) {
var fragment = document.createDocumentFragment();
sellerList.replaceChildren();
items.forEach(function (item, index) {
fragment.appendChild(createSellerCard(item, index));
});
sellerList.appendChild(fragment);
}
function createSkeletonCard() {
var item = document.createElement('li');
item.className = 'seller-skeleton-card';
item.innerHTML =
'
' +
'' +
'' +
'' +
'';
return item;
}
function renderSkeletons() {
var fragment = document.createDocumentFragment();
for (var index = 0; index < 5; index += 1) {
fragment.appendChild(createSkeletonCard());
}
skeletonList.replaceChildren(fragment);
}
function setView(view) {
currentView = view;
loadingView.hidden = view !== 'loading';
emptyView.hidden = view !== 'empty';
errorView.hidden = view !== 'error';
sellerList.hidden = view !== 'list';
region.setAttribute('aria-busy', String(view === 'loading'));
}
function requestCoinSellers() {
if (isVisualFixture()) {
return Promise.resolve(visualFixturePayload());
}
if (
!window.HyAppAPI ||
!window.HyAppAPI.wallet ||
!window.HyAppAPI.wallet.listCoinSellers
) {
return Promise.reject(new Error('coin_seller_api_unavailable'));
}
return window.HyAppAPI.wallet.listCoinSellers();
}
function loadCoinSellers() {
var sequence = ++requestSequence;
currentItems = [];
setView('loading');
requestCoinSellers()
.then(function (payload) {
// Retry 或后续刷新可能先于旧请求完成;只允许最新序列提交 UI,
// 防止慢失败覆盖已经成功的新列表。
if (sequence !== requestSequence) return;
currentItems = normalizePayload(payload);
if (!currentItems.length) {
setView('empty');
return;
}
renderSellers(currentItems);
setView('list');
})
.catch(function (error) {
if (sequence !== requestSequence) return;
currentItems = [];
setView('error');
if (window.console && window.console.warn) {
window.console.warn('[coin-seller:fami]', error);
}
});
}
retryButton.addEventListener('click', loadCoinSellers);
window.addEventListener('hyapp:i18n-ready', function () {
// DOM 模板里的动态 ID 与可访问标签不由 data-i18n 自动插值;语言切换后
// 重新渲染当前成功列表即可保持视觉与读屏文案一致,不重新发业务请求。
if (currentView === 'list' && currentItems.length) {
renderSellers(currentItems);
}
});
renderSkeletons();
loadCoinSellers();
})();