diff --git a/activity/invite/index.html b/activity/invite/index.html index e3c48b5..9435a1d 100644 --- a/activity/invite/index.html +++ b/activity/invite/index.html @@ -253,9 +253,9 @@ })(); - + - + diff --git a/activity/invite/script.js b/activity/invite/script.js index f4bb523..672babf 100644 --- a/activity/invite/script.js +++ b/activity/invite/script.js @@ -20,13 +20,6 @@ 'ios_url', 'url', ]; - var rankRows = Array.from({ length: 30 }, function (_, index) { - return { - rank: index + 1, - name: 'hanneem', - score: '123', - }; - }); var params = new URLSearchParams(window.location.search); var state = { inviteCode: firstParam(INVITE_CODE_KEYS), @@ -34,6 +27,9 @@ inviterAvatar: firstParam(SHARE_AVATAR_KEYS), inviteLink: '', inviteRewardStatus: null, + rankRows: [], + rankLoaded: false, + rankLoading: false, claiming: {}, }; @@ -258,6 +254,36 @@ if (node) node.textContent = value; } + function normalizeRankPayload(payload) { + if (!payload || typeof payload !== 'object') return []; + var items = field(payload, 'entries', 'entries'); + if (!Array.isArray(items)) items = field(payload, 'items', 'items'); + if (!Array.isArray(items)) return []; + return items + .map(function (item, index) { + var user = field(item, 'user', 'user') || {}; + var userID = textValue(field(item, 'user_id', 'userId')); + return { + rank: + numberValue(field(item, 'rank_no', 'rankNo')) || + index + 1, + name: + textValue(field(user, 'username', 'username')) || + textValue( + field(user, 'display_user_id', 'displayUserId') + ) || + (userID ? 'ID ' + userID : '--'), + avatar: textValue(field(user, 'avatar', 'avatar')), + score: numberValue( + field(item, 'valid_invite_count', 'validInviteCount') + ), + }; + }) + .filter(function (item) { + return item.rank > 0 && item.score > 0; + }); + } + function pad(value) { return String(Math.max(0, value)).padStart(2, '0'); } @@ -281,8 +307,21 @@ function renderRank() { var list = $('#rankList'); + if (!list) return; list.textContent = ''; - rankRows.forEach(function (item) { + if (state.rankLoading && !state.rankRows.length) { + list.appendChild( + emptyTaskNode(t('invite.rankLoading', 'Loading rank...')) + ); + return; + } + if (!state.rankRows.length) { + list.appendChild( + emptyTaskNode(t('invite.noRankData', 'No rank data')) + ); + return; + } + state.rankRows.forEach(function (item) { var row = document.createElement('article'); var rank = document.createElement('span'); var medal = document.createElement('img'); @@ -306,10 +345,14 @@ rank.textContent = item.rank; } - avatar.src = './assets/rank-avatar.png'; + avatar.src = item.avatar || './assets/rank-avatar.png'; + avatar.onerror = function () { + avatar.onerror = null; + avatar.src = './assets/rank-avatar.png'; + }; avatar.alt = ''; name.textContent = item.name; - score.textContent = formatPeople(item.score); + score.textContent = formatPeople(formatNumber(item.score)); row.appendChild(rank); row.appendChild( @@ -583,6 +626,36 @@ }); } + function loadInviteActivityLeaderboard(force) { + var api = window.HyAppAPI && window.HyAppAPI.inviteActivityReward; + if ( + state.rankLoading || + (state.rankLoaded && !force) || + !api || + !api.leaderboard || + !window.HyAppAPI.getAccessToken() + ) { + renderRank(); + return Promise.resolve(); + } + state.rankLoading = true; + renderRank(); + return api + .leaderboard({ page: 1, page_size: 50 }) + .then(function (payload) { + state.rankRows = normalizeRankPayload(payload); + state.rankLoaded = true; + state.rankLoading = false; + renderRank(); + }) + .catch(function () { + state.rankRows = []; + state.rankLoaded = true; + state.rankLoading = false; + renderRank(); + }); + } + function claimInviteReward(tier) { var api = window.HyAppAPI && window.HyAppAPI.inviteActivityReward; var rewardType = tierRewardType(tier); @@ -639,6 +712,7 @@ $('#transportPane').classList.toggle('is-active', !isQuota); $('#quotaPane').classList.toggle('is-active', isQuota); $('#rankPanel').hidden = !isQuota; + if (isQuota) loadInviteActivityLeaderboard(false); } function copyTextFrom(selector) { diff --git a/common/api.js b/common/api.js index 337d812..8c79cf0 100644 --- a/common/api.js +++ b/common/api.js @@ -681,6 +681,12 @@ var default_api = 'https://api.global-interaction.com/'; method: 'GET', }); }, + leaderboard: function (params) { + return request('/api/v1/activities/invite-reward/leaderboard', { + method: 'GET', + query: params || {}, + }); + }, claim: function (payload) { return request('/api/v1/activities/invite-reward/claim', { method: 'POST', diff --git a/common/locales/ar.json b/common/locales/ar.json index 18f9064..e3f2344 100644 --- a/common/locales/ar.json +++ b/common/locales/ar.json @@ -104,6 +104,8 @@ "invite.validInviteTaskDescription": "ادع {threshold} أصدقاء مؤهلين، المكافأة {reward} عملة", "invite.rank": "الترتيب", "invite.peopleSuffix": "{value} أشخاص", + "invite.rankLoading": "جار تحميل الترتيب...", + "invite.noRankData": "لا توجد بيانات ترتيب", "invite.copied": "تم النسخ", "invite.copyFailed": "فشل النسخ", "invite.notAvailable": "غير متاح الآن", diff --git a/common/locales/en.json b/common/locales/en.json index b84948f..66df4e2 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -104,6 +104,8 @@ "invite.validInviteTaskDescription": "Invite {threshold} valid friends, reward {reward} coins", "invite.rank": "Rank", "invite.peopleSuffix": "{value} people", + "invite.rankLoading": "Loading rank...", + "invite.noRankData": "No rank data", "invite.copied": "Copied", "invite.copyFailed": "Copy failed", "invite.notAvailable": "Not available yet", diff --git a/common/locales/es.json b/common/locales/es.json index 5f2633a..fc7fc1d 100644 --- a/common/locales/es.json +++ b/common/locales/es.json @@ -104,6 +104,8 @@ "invite.validInviteTaskDescription": "Invita a {threshold} amigos válidos, recompensa {reward} monedas", "invite.rank": "Ranking", "invite.peopleSuffix": "{value} personas", + "invite.rankLoading": "Cargando ranking...", + "invite.noRankData": "No hay datos de ranking", "invite.copied": "Copiado", "invite.copyFailed": "Error al copiar", "invite.notAvailable": "Aún no disponible", diff --git a/common/locales/id.json b/common/locales/id.json index 4b6cee9..0c11ca5 100644 --- a/common/locales/id.json +++ b/common/locales/id.json @@ -74,6 +74,8 @@ "invite.validInviteTaskDescription": "Undang {threshold} teman valid, hadiah {reward} koin", "invite.rank": "Peringkat", "invite.peopleSuffix": "{value} orang", + "invite.rankLoading": "Memuat peringkat...", + "invite.noRankData": "Belum ada data peringkat", "invite.copied": "Disalin", "invite.copyFailed": "Gagal menyalin", "invite.notAvailable": "Belum tersedia", diff --git a/common/locales/tr.json b/common/locales/tr.json index 809a62c..0000aa3 100644 --- a/common/locales/tr.json +++ b/common/locales/tr.json @@ -74,6 +74,8 @@ "invite.validInviteTaskDescription": "{threshold} geçerli arkadaş davet et, ödül {reward} coin", "invite.rank": "Sıralama", "invite.peopleSuffix": "{value} kişi", + "invite.rankLoading": "Sıralama yükleniyor...", + "invite.noRankData": "Sıralama verisi yok", "invite.copied": "Kopyalandı", "invite.copyFailed": "Kopyalama başarısız", "invite.notAvailable": "Henüz kullanılamaz", diff --git a/common/locales/zh.json b/common/locales/zh.json index be62756..e880527 100644 --- a/common/locales/zh.json +++ b/common/locales/zh.json @@ -74,6 +74,8 @@ "invite.validInviteTaskDescription": "邀请 {threshold} 位有效用户,奖励 {reward} 金币", "invite.rank": "排行榜", "invite.peopleSuffix": "{value}人", + "invite.rankLoading": "排行榜加载中...", + "invite.noRankData": "暂无排行榜数据", "invite.copied": "已复制", "invite.copyFailed": "复制失败", "invite.notAvailable": "暂不可领取",