vip.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <!-- vipActive false 未激活
  4. 激活条件
  5. 1 invited(布尔) 邀请一名好友
  6. 2 deposited(布尔) 是否累计充值10 USDT
  7. *满足以上条件即可激活vip权益
  8. -->
  9. <head>
  10. <meta charset="UTF-8">
  11. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  12. <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  13. <title></title>
  14. <script src="/js/url-cache.js"></script>
  15. <script src="/token.js"></script>
  16. <script>
  17. // theme.js 会接管完整主题逻辑,这里仅做无闪烁预处理
  18. (function () {
  19. var theme = '';
  20. try {
  21. var params = new URLSearchParams(window.location.search);
  22. var t = params.get('theme');
  23. if (t === 'dark' || t === 'light') theme = t;
  24. } catch (e) { }
  25. if (!theme && window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
  26. theme = 'light';
  27. }
  28. document.documentElement.classList.add('theme-' + (theme || 'light'));
  29. })();
  30. </script>
  31. <link rel="stylesheet" href="/css/business-theme.css">
  32. <link rel="stylesheet" href="/css/vip.css">
  33. </head>
  34. <body>
  35. <div class="container">
  36. <!--
  37. <div class="header">
  38. <svg id="backButton" width="16" height="16" viewBox="0 0 16 16" fill="none"
  39. xmlns="http://www.w3.org/2000/svg">
  40. <path d="M10.4 13.6L4.8 8L10.4 2.4" stroke="white" stroke-width="1.6" stroke-linecap="round"
  41. stroke-linejoin="round" />
  42. </svg>
  43. <div class="header-title" data-i18n="headerTitle">BEX会员等级</div>
  44. <div></div>
  45. </div> -->
  46. <div class="vip-card">
  47. <i class="vip-bg"></i>
  48. <div class="vip-box">
  49. <div class="vip-card-header">
  50. <div class="vip-level-badge">
  51. <div class="vip-level-title" data-i18n="vipLevelTitle">会员等级</div>
  52. <div class="vip-level-text" data-field="vipLevel"></div>
  53. </div>
  54. </div>
  55. <!-- 已激活:进度条状态 -->
  56. <div class="vip-active-content" id="vipActiveContent">
  57. <div class="progress-items">
  58. <div class="progress-item">
  59. <div class="progress-label" data-i18n="progressContribution"> <span
  60. data-field="contribution"></span> </div>
  61. <div class="progress-bar">
  62. <div class="progress-bar-fill" data-field="contributionProgress"></div>
  63. </div>
  64. </div>
  65. <div class="progress-item">
  66. <div class="progress-label" data-i18n="progressStaking"><span data-field="staking"></span>
  67. </div>
  68. <div class="progress-bar">
  69. <div class="progress-bar-fill" data-field="stakingProgress"></div>
  70. </div>
  71. </div>
  72. </div>
  73. <div class="vip-card-tip" data-i18n="vipTip">*满足以上条件即可升级至 VIP3</div>
  74. </div>
  75. <!-- 未激活:条件展示状态 -->
  76. <div class="vip-inactive-content" id="vipInactiveContent" style="display:none;">
  77. <div class="activation-conditions">
  78. <div class="activation-condition">
  79. <div class="condition-icon" id="invitedIcon">
  80. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  81. <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
  82. <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
  83. stroke-linecap="round" stroke-linejoin="round" />
  84. </svg>
  85. </div>
  86. <span class="condition-label" data-i18n="activationInvite">邀请一名好友</span>
  87. <span class="condition-status" data-field="invitedStatus"></span>
  88. </div>
  89. <div class="activation-condition">
  90. <div class="condition-icon" id="depositedIcon">
  91. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  92. <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
  93. <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
  94. stroke-linecap="round" stroke-linejoin="round" />
  95. </svg>
  96. </div>
  97. <span class="condition-label" data-i18n="activationDeposit">累计充值10 USDT</span>
  98. <span class="condition-status" data-field="depositedStatus"></span>
  99. </div>
  100. </div>
  101. <div class="vip-card-tip" data-i18n="vipActiveTip">*满足以上条件即可解锁VIP权益</div>
  102. </div>
  103. </div>
  104. </div>
  105. <!-- <div class="section-title" data-i18n="benefitsTitle">等级权益</div> -->
  106. <div class="benefits-tabs" id="benefitsTabs">
  107. <div class="benefits-tab active" data-tab="requirements" data-i18n="benefitsTabRequirements">等级权益</div>
  108. <div class="benefits-tab" data-tab="benefits" data-i18n="benefitsTabBenefits">升级规则</div>
  109. </div>
  110. <div class="benefits-table" id="benefitsTable">
  111. <div>
  112. <div class="benefits-header requirements-header" style="display: none;">
  113. <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
  114. <div data-i18n="benefitsContribution" class="td">贡献值</div>
  115. <div data-i18n="benefitsStaking" class="td">个人质押要求</div>
  116. </div>
  117. <div class="benefits-header benefits-ben-header">
  118. <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
  119. <div data-i18n="benefitsTeamReward" class="td">团队质押动态奖励</div>
  120. <div data-i18n="benefitsTradeReturn" class="td">交易返佣比例</div>
  121. </div>
  122. <div id="benefitsBody" style="display: none;"></div>
  123. <div id="benefitsBodyAlt"></div>
  124. </div>
  125. </div>
  126. <div class="contribution-section" id="contributionSection">
  127. <div class="contribution-title" data-i18n="contributionTitle">贡献值获取方式</div>
  128. <div class="contribution-card" id="contributionCard">
  129. </div>
  130. <div class="contribution-tip" data-i18n="contributionTip">*降级规则: 所有级别如贡献值下降,原级别保留 1 个月;下个月如继续未达标,则降级至对应实际级别
  131. </div>
  132. </div>
  133. <div class="description-modal" id="descriptionModal" style="display: none;">
  134. <div class="modal-overlay" id="modalOverlay"></div>
  135. <div class="modal-content">
  136. <div class="modal-header">
  137. <span class="modal-title" data-i18n="modalTitle">说明</span>
  138. <svg class="modal-close" id="modalClose" width="24" height="24" viewBox="0 0 24 24" fill="none"
  139. stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  140. <line x1="18" y1="6" x2="6" y2="18"></line>
  141. <line x1="6" y1="6" x2="18" y2="18"></line>
  142. </svg>
  143. </div>
  144. <div class="modal-body">
  145. <p id="modalDescription"></p>
  146. </div>
  147. </div>
  148. </div>
  149. <div class="loading-overlay" id="loadingOverlay" style="display: flex;">
  150. <div class="loading-content">
  151. <div class="loading-spinner"></div>
  152. <span class="loading-text" data-i18n="loadingText">加载中...</span>
  153. </div>
  154. </div>
  155. <div class="error-overlay" id="errorOverlay" style="display: none;">
  156. <div class="error-content">
  157. <div class="error-icon">!</div>
  158. <span class="error-text" data-i18n="errorText">加载失败,请稍后重试</span>
  159. <div class="error-actions">
  160. <button class="error-retry" id="retryBtn" data-i18n="retryBtn">重试</button>
  161. <button class="error-retry" id="errorBackBtn" data-i18n="errorBackBtn">返回</button>
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. <script src="/js/common.js"></script>
  167. <script>
  168. (function () {
  169. function applyTranslations(lang, nextVipLevel) {
  170. BEXI18n.applyTranslations('vip', {
  171. params: { nextVipLevel: 'VIP' + (nextVipLevel || '') },
  172. htmlKeys: ['progressContribution', 'progressStaking', 'vipTip', 'contributionPersonal', 'contributionTeam', 'contributionSpot', 'contributionFutures']
  173. });
  174. }
  175. const API_URL = '/api/v1';
  176. const loadingOverlay = document.getElementById('loadingOverlay');
  177. const errorOverlay = document.getElementById('errorOverlay');
  178. const retryBtn = document.getElementById('retryBtn');
  179. const errorBackBtn = document.getElementById('errorBackBtn');
  180. const benefitsBody = document.getElementById('benefitsBody');
  181. const benefitsBodyAlt = document.getElementById('benefitsBodyAlt');
  182. // const backButton = document.getElementById('backButton');
  183. const benefitsTabs = document.querySelectorAll('.benefits-tab');
  184. const contributionCard = document.getElementById('contributionCard');
  185. const descriptionModal = document.getElementById('descriptionModal');
  186. const modalOverlay = document.getElementById('modalOverlay');
  187. const modalClose = document.getElementById('modalClose');
  188. const modalDescription = document.getElementById('modalDescription');
  189. benefitsTabs.forEach(tab => {
  190. tab.addEventListener('click', function () {
  191. const tabName = this.dataset.tab;
  192. benefitsTabs.forEach(t => t.classList.remove('active'));
  193. this.classList.add('active');
  194. if (tabName === 'requirements') {
  195. document.querySelector('.requirements-header.requirements-header').style.display = 'none';
  196. document.querySelector('.benefits-header.benefits-ben-header').style.display = 'grid';
  197. benefitsBody.style.display = 'none';
  198. benefitsBodyAlt.style.display = 'block';
  199. } else {
  200. document.querySelector('.requirements-header.requirements-header').style.display = 'grid';
  201. document.querySelector('.benefits-header.benefits-ben-header').style.display = 'none';
  202. benefitsBody.style.display = 'block';
  203. benefitsBodyAlt.style.display = 'none';
  204. }
  205. });
  206. });
  207. // backButton.addEventListener('click', function () {
  208. // if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
  209. // window.flutter_inappwebview.callHandler('closeWebview');
  210. // }
  211. // });
  212. errorBackBtn.addEventListener('click', function () {
  213. window.BEXBridge.closeWebview();
  214. });
  215. function showDescriptionModal(description) {
  216. modalDescription.textContent = description;
  217. descriptionModal.style.display = 'flex';
  218. }
  219. function hideDescriptionModal() {
  220. descriptionModal.style.display = 'none';
  221. }
  222. modalOverlay.addEventListener('click', hideDescriptionModal);
  223. modalClose.addEventListener('click', hideDescriptionModal);
  224. function showLoading() {
  225. loadingOverlay.style.display = 'flex';
  226. errorOverlay.style.display = 'none';
  227. }
  228. function hideLoading() {
  229. loadingOverlay.style.display = 'none';
  230. }
  231. function showError() {
  232. loadingOverlay.style.display = 'none';
  233. errorOverlay.style.display = 'flex';
  234. document.getElementById('benefitsTabs').style.display = 'none';
  235. document.getElementById('benefitsTable').style.display = 'none';
  236. document.getElementById('contributionSection').style.display = 'none';
  237. }
  238. function renderBenefitsRows(vipList) {
  239. if (!Array.isArray(vipList) || vipList.length === 0) {
  240. console.warn('VIP列表数据为空或格式错误');
  241. return;
  242. }
  243. benefitsBody.innerHTML = '';
  244. const lang = getCurrentLang();
  245. const translation = BEXI18n.getPage('vip', lang);
  246. const stakingUnit = translation.stakingUnit;
  247. vipList.forEach(item => {
  248. const row = document.createElement('div');
  249. row.className = 'benefits-row';
  250. row.dataset.vip = item.vipLevel;
  251. row.innerHTML = `
  252. <div class="vip-name td">
  253. <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
  254. <span>${item.vipName}</span>
  255. </div>
  256. <div class="td">${item.contributionValue}</div>
  257. <div class="td">${item.personalPledgeRequirements}${stakingUnit}</div>
  258. `;
  259. benefitsBody.appendChild(row);
  260. });
  261. }
  262. function renderBenefitsAltRows(vipList) {
  263. if (!Array.isArray(vipList) || vipList.length === 0) {
  264. console.warn('VIP列表数据为空或格式错误');
  265. return;
  266. }
  267. benefitsBodyAlt.innerHTML = '';
  268. const lang = getCurrentLang();
  269. const translation = BEXI18n.getPage('vip', lang);
  270. const stakingUnit = translation.stakingUnit;
  271. vipList.forEach(item => {
  272. const row = document.createElement('div');
  273. row.className = 'benefits-row';
  274. row.dataset.vip = item.vipLevel;
  275. row.innerHTML = `
  276. <div class="vip-name td">
  277. <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
  278. <span>${item.vipName}</span>
  279. </div>
  280. <div class="td">${item.teamStakingDynamicRewards}${translation.teamRewardUnit}</div>
  281. <div class="td">${item.transactionRefundRatio}${translation.tradeReturnUnit}</div>
  282. `;
  283. benefitsBodyAlt.appendChild(row);
  284. });
  285. }
  286. function renderContributionItems(contributionRuleConfigs) {
  287. const lang = getCurrentLang();
  288. const translation = BEXI18n.getPage('vip', lang);
  289. const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
  290. const currencySymbol = 'U';
  291. const ruleCodeMap = {
  292. 'PERSONAL_STAKE': { key: 'personalStaking', i18nKey: 'contributionPersonal', unit: stakingUnit },
  293. 'TEAM_STAKE': { key: 'teamStaking', i18nKey: 'contributionTeam', unit: stakingUnit },
  294. 'SPOT_TRADE': { key: 'spotTrading', i18nKey: 'contributionSpot', unit: currencySymbol },
  295. 'FUTURES_TRADE': { key: 'futuresTrading', i18nKey: 'contributionFutures', unit: currencySymbol }
  296. };
  297. const rules = {};
  298. const items = [];
  299. const order = ['PERSONAL_STAKE', 'TEAM_STAKE', 'SPOT_TRADE', 'FUTURES_TRADE'];
  300. contributionRuleConfigs.forEach(item => {
  301. const mapping = ruleCodeMap[item.ruleCode];
  302. if (mapping) {
  303. rules[mapping.key] = item.threshold + mapping.unit;
  304. items.push({
  305. ruleCode: item.ruleCode,
  306. key: mapping.key,
  307. i18nKey: mapping.i18nKey,
  308. value: item.threshold + mapping.unit,
  309. description: item.description,
  310. orderIndex: order.indexOf(item.ruleCode)
  311. });
  312. }
  313. });
  314. items.sort((a, b) => a.orderIndex - b.orderIndex);
  315. contributionCard.innerHTML = '';
  316. items.forEach(item => {
  317. const itemDiv = document.createElement('div');
  318. itemDiv.className = 'contribution-item';
  319. itemDiv.innerHTML = translation[item.i18nKey].replace(
  320. new RegExp(`<span class="highlight" data-field="rules\\.${item.key}">[^<]*</span>`, 'g'),
  321. `<span class="highlight">${item.value}</span>`
  322. );
  323. if (item.description) {
  324. const infoIcon = document.createElement('div');
  325. infoIcon.className = 'contribution-info-icon';
  326. infoIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  327. <circle cx="12" cy="12" r="10"></circle>
  328. <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
  329. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  330. </svg>`;
  331. infoIcon.addEventListener('click', () => showDescriptionModal(item.description));
  332. itemDiv.appendChild(infoIcon);
  333. }
  334. contributionCard.appendChild(itemDiv);
  335. });
  336. return rules;
  337. }
  338. function insertData(data) {
  339. const vipBenefitManagements = data?.vipBenefitManagements || [];
  340. const contributionRuleConfigs = data?.contributionRuleConfigs || [];
  341. const vipActive = data?.vipActive !== false;
  342. const invited = data?.invited || false;
  343. const deposited = data?.deposited || false;
  344. if (vipBenefitManagements.length > 0) {
  345. renderBenefitsRows(vipBenefitManagements);
  346. renderBenefitsAltRows(vipBenefitManagements);
  347. }
  348. const rules = renderContributionItems(contributionRuleConfigs);
  349. const lang = getCurrentLang();
  350. const translation = BEXI18n.getPage('vip', lang);
  351. const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
  352. // 已激活 / 未激活 状态切换
  353. const vipActiveContent = document.getElementById('vipActiveContent');
  354. const vipInactiveContent = document.getElementById('vipInactiveContent');
  355. if (!vipActive) {
  356. // 未激活:显示条件卡片
  357. vipActiveContent.style.display = 'none';
  358. vipInactiveContent.style.display = 'block';
  359. // 设置等级文字为"未激活"
  360. const vipLevelEl = document.querySelector('[data-field="vipLevel"]');
  361. if (vipLevelEl) {
  362. vipLevelEl.textContent = translation.vipInactive || '未激活';
  363. vipLevelEl.classList.add('inactive');
  364. }
  365. // 设置邀请好友条件
  366. const invitedIcon = document.getElementById('invitedIcon');
  367. const invitedStatus = document.querySelector('[data-field="invitedStatus"]');
  368. if (invitedIcon && invitedStatus) {
  369. invitedIcon.className = 'condition-icon ' + (invited ? 'completed' : 'pending');
  370. invitedStatus.textContent = invited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
  371. }
  372. // 设置充值条件
  373. const depositedIcon = document.getElementById('depositedIcon');
  374. const depositedStatus = document.querySelector('[data-field="depositedStatus"]');
  375. if (depositedIcon && depositedStatus) {
  376. depositedIcon.className = 'condition-icon ' + (deposited ? 'completed' : 'pending');
  377. depositedStatus.textContent = deposited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
  378. }
  379. return;
  380. }
  381. // 已激活:显示进度条(原有逻辑)
  382. vipActiveContent.style.display = 'block';
  383. vipInactiveContent.style.display = 'none';
  384. const currentVipLevel = data?.vipLevel || 0;
  385. const personalValue = data?.personalValue || 0;
  386. const personalStakeValue = data?.personalStakeValue || 0;
  387. const currentLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel);
  388. const nextLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel + 1);
  389. let contributionProgress = 0;
  390. let stakingProgress = 0;
  391. let contributionNeeded = 0;
  392. let stakingNeeded = 0;
  393. if (nextLevelInfo) {
  394. const currentContribution = currentLevelInfo ? currentLevelInfo.contributionValue || 0 : 0;
  395. const currentStaking = currentLevelInfo ? currentLevelInfo.personalPledgeRequirements || 0 : 0;
  396. const nextContribution = nextLevelInfo.contributionValue || 0;
  397. const nextStaking = nextLevelInfo.personalPledgeRequirements || 0;
  398. const contributionDelta = mathSub(nextContribution, currentContribution);
  399. const stakingDelta = mathSub(nextStaking, currentStaking);
  400. const currentContributionIncrement = mathSub(personalValue, currentContribution);
  401. const currentStakingIncrement = mathSub(personalStakeValue, currentStaking);
  402. contributionNeeded = Math.max(0, mathSub(contributionDelta, currentContributionIncrement));
  403. stakingNeeded = Math.max(0, mathSub(stakingDelta, currentStakingIncrement));
  404. contributionProgress = contributionDelta > 0 ? Math.min(100, Math.max(0, (currentContributionIncrement / contributionDelta) * 100)) : 0;
  405. stakingProgress = stakingDelta > 0 ? Math.min(100, Math.max(0, (currentStakingIncrement / stakingDelta) * 100)) : 0;
  406. } else {
  407. contributionProgress = 100;
  408. stakingProgress = 100;
  409. }
  410. const elements = document.querySelectorAll('[data-field]');
  411. elements.forEach(element => {
  412. const field = element.dataset.field;
  413. let value = getNestedValue(data, field);
  414. if (field === 'vipLevel' && data.vipName) {
  415. value = data.vipName;
  416. }
  417. if (field.startsWith('rules.')) {
  418. const ruleKey = field.replace('rules.', '');
  419. value = rules[ruleKey];
  420. }
  421. if (field === 'contribution') {
  422. value = contributionNeeded + translation.contributionValueUnit;
  423. }
  424. if (field === 'staking') {
  425. value = stakingNeeded + stakingUnit;
  426. }
  427. if (field === 'personalValue') {
  428. value = value + translation.contributionValueUnit;
  429. }
  430. if (field === 'personalStakeValue') {
  431. value = value + stakingUnit;
  432. }
  433. if (field === 'contributionProgress') {
  434. value = contributionProgress.toFixed(0);
  435. }
  436. if (field === 'stakingProgress') {
  437. value = stakingProgress.toFixed(0);
  438. }
  439. if (value !== undefined && value !== null) {
  440. if (field.includes('Progress')) {
  441. element.style.width = value + '%';
  442. } else {
  443. element.textContent = value;
  444. }
  445. }
  446. });
  447. }
  448. function getNestedValue(obj, path) {
  449. return path.split('.').reduce((current, key) => {
  450. return current && current[key] !== undefined ? current[key] : undefined;
  451. }, obj);
  452. }
  453. var token = window.WELFARE_TOKEN || '';
  454. function getDefaultHeaders() {
  455. return {
  456. 'Content-Type': 'application/json',
  457. 'Accept': 'application/json',
  458. 'X-Requested-With': 'XMLHttpRequest',
  459. 'localeName': getCurrentLang(),
  460. ...globalHeaders
  461. };
  462. }
  463. async function fetchData() {
  464. showLoading();
  465. try {
  466. const data = await window.BEXApi.requestWithAuth(API_URL + '/userVipInfo/info', {
  467. method: 'GET',
  468. headers: getDefaultHeaders()
  469. }, token) || {};
  470. const currentVipLevel = data.vipLevel || 0;
  471. const nextVipLevel = currentVipLevel + 1;
  472. applyTranslations(getCurrentLang(), nextVipLevel);
  473. insertData(data);
  474. } catch (error) {
  475. console.error('Data fetch error:', error);
  476. showError()
  477. } finally {
  478. hideLoading();
  479. }
  480. }
  481. retryBtn.addEventListener('click', fetchData);
  482. document.addEventListener('DOMContentLoaded', async function () {
  483. const lang = getCurrentLang();
  484. applyTranslations(lang);
  485. try {
  486. token = await window.BEXBridge.getToken();
  487. } catch (error) {
  488. // showError()
  489. }
  490. fetchData();
  491. });
  492. })();
  493. </script>
  494. </body>
  495. </html>