app邀请
BIN
activity/invite/assets/generated/copy-icon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
activity/invite/assets/generated/invite-input-bg.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
activity/invite/assets/generated/inviter-card-bg.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
activity/invite/assets/generated/modal-bg.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
activity/invite/assets/generated/modal-bottom.png
Normal file
|
After Width: | Height: | Size: 676 KiB |
BIN
activity/invite/assets/generated/modal-button-active.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
activity/invite/assets/generated/modal-button-inactive.png
Normal file
|
After Width: | Height: | Size: 871 KiB |
BIN
activity/invite/assets/generated/modal-middle.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
activity/invite/assets/generated/modal-top.png
Normal file
|
After Width: | Height: | Size: 670 KiB |
BIN
activity/invite/assets/generated/panel-a-monthly-bg.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
activity/invite/assets/generated/panel-a-monthly-quota-bg.png
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
BIN
activity/invite/assets/generated/panel-a-stats-bg.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
activity/invite/assets/generated/panel-b-rank-bg.png
Normal file
|
After Width: | Height: | Size: 6.3 MiB |
BIN
activity/invite/assets/generated/reward-success-visual.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
activity/invite/assets/generated/reward-topup-visual.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
activity/invite/assets/generated/search-icon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
@ -7,7 +7,7 @@
|
|||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||||
/>
|
/>
|
||||||
<title>Invite Friends</title>
|
<title>Invite Friends</title>
|
||||||
<link rel="stylesheet" href="./style.css?v=20260611-1" />
|
<link rel="stylesheet" href="./style.css?v=20260629-bind-invite" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main
|
<main
|
||||||
@ -72,38 +72,28 @@
|
|||||||
(Earn for every friend you invite)
|
(Earn for every friend you invite)
|
||||||
</p>
|
</p>
|
||||||
<div class="reward-grid">
|
<div class="reward-grid">
|
||||||
<article class="reward-item">
|
<article class="reward-item reward-item-success">
|
||||||
<img
|
<img
|
||||||
class="reward-orb"
|
class="reward-visual"
|
||||||
src="./assets/reward-orb-bg.png"
|
src="./assets/generated/reward-success-visual.png"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<img
|
<strong id="rewardInviteAmount"> +1000 </strong>
|
||||||
class="reward-gold"
|
<span data-i18n="invite.successfulInvite">
|
||||||
src="./assets/gold-stack.png"
|
Successful Invite
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<strong>+2000</strong>
|
|
||||||
<span data-i18n="invite.invitationReceived">
|
|
||||||
Invitation received
|
|
||||||
</span>
|
</span>
|
||||||
</article>
|
</article>
|
||||||
<img class="plus" src="./assets/plus.svg" alt="+" />
|
<img class="plus" src="./assets/plus.svg" alt="+" />
|
||||||
<article class="reward-item">
|
<article class="reward-item reward-item-topup">
|
||||||
<img
|
<img
|
||||||
class="reward-orb"
|
class="reward-visual"
|
||||||
src="./assets/reward-orb-bg.png"
|
src="./assets/generated/reward-topup-visual.png"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<img
|
<strong id="rewardTopUpPercent">1%</strong>
|
||||||
class="reward-gold"
|
<span data-i18n="invite.friendTopUp"
|
||||||
src="./assets/gold-stack.png"
|
>Friend's Top-Up</span
|
||||||
alt=""
|
>
|
||||||
/>
|
|
||||||
<strong>+1000</strong>
|
|
||||||
<span data-i18n="invite.topUpViaFriends">
|
|
||||||
Top up via friends
|
|
||||||
</span>
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -116,31 +106,39 @@
|
|||||||
(Copy your invitation link and share it with your friends)
|
(Copy your invitation link and share it with your friends)
|
||||||
</p>
|
</p>
|
||||||
<div class="invite-fields">
|
<div class="invite-fields">
|
||||||
<div class="link-box" id="inviteLink">
|
<div class="invite-field">
|
||||||
http://yourappbalbalanal
|
<div class="link-box" id="inviteLink">
|
||||||
|
http://yourappbalbalanal
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="copy-icon-button"
|
||||||
|
id="copyLinkButton"
|
||||||
|
type="button"
|
||||||
|
aria-label="Copy invitation link"
|
||||||
|
data-i18n-aria="invite.copyInviteLink"
|
||||||
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="link-box invite-code" id="inviteCode">
|
<div class="invite-field is-code">
|
||||||
ABC123
|
<div class="link-box invite-code" id="inviteCode">
|
||||||
|
ABC123
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="copy-icon-button"
|
||||||
|
id="copyCodeButton"
|
||||||
|
type="button"
|
||||||
|
aria-label="Copy invitation code"
|
||||||
|
data-i18n-aria="invite.copyInviteCode"
|
||||||
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="invite-actions">
|
<button
|
||||||
<button
|
class="ornate-button bind-invite-button"
|
||||||
class="ornate-button"
|
id="bindInviteButton"
|
||||||
id="copyLinkButton"
|
type="button"
|
||||||
type="button"
|
data-i18n="invite.linkInviter"
|
||||||
data-i18n="invite.copy"
|
>
|
||||||
>
|
Link Inviter
|
||||||
Copy
|
</button>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ornate-button small-copy-button"
|
|
||||||
id="copyCodeButton"
|
|
||||||
type="button"
|
|
||||||
data-i18n="invite.copy"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="ornate-panel stats-panel" aria-label="Invite stats">
|
<section class="ornate-panel stats-panel" aria-label="Invite stats">
|
||||||
@ -234,6 +232,67 @@
|
|||||||
<h2 data-i18n="invite.rank">Rank</h2>
|
<h2 data-i18n="invite.rank">Rank</h2>
|
||||||
<div class="rank-list" id="rankList"></div>
|
<div class="rank-list" id="rankList"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="invite-modal"
|
||||||
|
id="bindInviteModal"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="bindInviteTitle"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="bind-card">
|
||||||
|
<h2 id="bindInviteTitle" data-i18n="invite.bindInviteTitle">
|
||||||
|
Bind Referrer
|
||||||
|
</h2>
|
||||||
|
<div class="bind-input-state" id="bindInputCard">
|
||||||
|
<label class="bind-input-shell">
|
||||||
|
<input
|
||||||
|
id="bindInviteInput"
|
||||||
|
type="text"
|
||||||
|
inputmode="text"
|
||||||
|
autocapitalize="characters"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="Enter Invitation Code"
|
||||||
|
data-i18n-placeholder="invite.bindInvitePlaceholder"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bind-result-state" id="bindResultCard" hidden>
|
||||||
|
<article class="inviter-card">
|
||||||
|
<img
|
||||||
|
id="inviterAvatar"
|
||||||
|
src="./assets/rank-avatar.png"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<strong id="inviterName">--</strong>
|
||||||
|
<span id="inviterDisplayId">--</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bind-actions">
|
||||||
|
<button
|
||||||
|
class="bind-action-button bind-cancel-button"
|
||||||
|
id="bindCancelButton"
|
||||||
|
type="button"
|
||||||
|
data-i18n="invite.cancel"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="bind-action-button bind-confirm-button"
|
||||||
|
id="confirmBindButton"
|
||||||
|
type="button"
|
||||||
|
data-i18n="invite.confirm"
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -253,9 +312,9 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script src="../../common/i18n.js?v=20260606-1"></script>
|
<script src="../../common/i18n.js?v=20260606-1"></script>
|
||||||
<script src="../../common/api.js?v=20260611-invite-rank"></script>
|
<script src="../../common/api.js?v=20260630-invite-code"></script>
|
||||||
<script src="../../common/params.js?v=20260611-invite-referrer"></script>
|
<script src="../../common/params.js?v=20260611-invite-referrer"></script>
|
||||||
<script src="../../common/toast.js"></script>
|
<script src="../../common/toast.js"></script>
|
||||||
<script src="./script.js?v=20260611-invite-rank"></script>
|
<script src="./script.js?v=20260630-invite-code"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -26,10 +26,15 @@
|
|||||||
inviterName: firstParam(SHARE_NAME_KEYS),
|
inviterName: firstParam(SHARE_NAME_KEYS),
|
||||||
inviterAvatar: firstParam(SHARE_AVATAR_KEYS),
|
inviterAvatar: firstParam(SHARE_AVATAR_KEYS),
|
||||||
inviteLink: '',
|
inviteLink: '',
|
||||||
|
currentUser: null,
|
||||||
|
pendingInviteCode: '',
|
||||||
|
searchedInviter: null,
|
||||||
inviteRewardStatus: null,
|
inviteRewardStatus: null,
|
||||||
rankRows: [],
|
rankRows: [],
|
||||||
rankLoaded: false,
|
rankLoaded: false,
|
||||||
rankLoading: false,
|
rankLoading: false,
|
||||||
|
searchingInviter: false,
|
||||||
|
bindingInviter: false,
|
||||||
claiming: {},
|
claiming: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,6 +54,10 @@
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeInviteCodeInput(value) {
|
||||||
|
return textValue(value).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
function readPath(source, path) {
|
function readPath(source, path) {
|
||||||
var value = source;
|
var value = source;
|
||||||
for (var i = 0; i < path.length; i += 1) {
|
for (var i = 0; i < path.length; i += 1) {
|
||||||
@ -66,6 +75,25 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentUserPayload(data, profile) {
|
||||||
|
var target = profile || data || {};
|
||||||
|
return Boolean(
|
||||||
|
target.user_id ||
|
||||||
|
target.userId ||
|
||||||
|
target.display_user_id ||
|
||||||
|
target.displayUserId ||
|
||||||
|
target.username ||
|
||||||
|
target.nickname ||
|
||||||
|
target.profile_completed !== undefined ||
|
||||||
|
target.profileCompleted !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeCurrentUser(profile) {
|
||||||
|
if (!profile || typeof profile !== 'object') return;
|
||||||
|
state.currentUser = Object.assign({}, state.currentUser || {}, profile);
|
||||||
|
}
|
||||||
|
|
||||||
function currentLandingURL() {
|
function currentLandingURL() {
|
||||||
return new URL('../invite-landing/', window.location.href);
|
return new URL('../invite-landing/', window.location.href);
|
||||||
}
|
}
|
||||||
@ -112,6 +140,7 @@
|
|||||||
function applyCurrentUser(payload) {
|
function applyCurrentUser(payload) {
|
||||||
var data = readOverviewPayload(payload || {});
|
var data = readOverviewPayload(payload || {});
|
||||||
var profile = data.profile || data;
|
var profile = data.profile || data;
|
||||||
|
if (currentUserPayload(data, profile)) mergeCurrentUser(profile);
|
||||||
var inviteCode = firstPath(data, [
|
var inviteCode = firstPath(data, [
|
||||||
['invite', 'my_invite_code'],
|
['invite', 'my_invite_code'],
|
||||||
['invite', 'myInviteCode'],
|
['invite', 'myInviteCode'],
|
||||||
@ -171,6 +200,13 @@
|
|||||||
.then(applyCurrentUser)
|
.then(applyCurrentUser)
|
||||||
.catch(function () {});
|
.catch(function () {});
|
||||||
}
|
}
|
||||||
|
function loadMeProfile() {
|
||||||
|
if (!hasToken || !userAPI || !userAPI.me) return Promise.resolve();
|
||||||
|
return userAPI
|
||||||
|
.me()
|
||||||
|
.then(applyCurrentUser)
|
||||||
|
.catch(function () {});
|
||||||
|
}
|
||||||
|
|
||||||
if (window.HyAppParams && window.HyAppParams.loadUser) {
|
if (window.HyAppParams && window.HyAppParams.loadUser) {
|
||||||
return userPromise
|
return userPromise
|
||||||
@ -179,7 +215,8 @@
|
|||||||
})
|
})
|
||||||
.then(function (current) {
|
.then(function (current) {
|
||||||
if (current && current.user) applyCurrentUser(current.user);
|
if (current && current.user) applyCurrentUser(current.user);
|
||||||
});
|
})
|
||||||
|
.then(loadMeProfile);
|
||||||
}
|
}
|
||||||
if (userAPI) {
|
if (userAPI) {
|
||||||
if (userAPI.overview) {
|
if (userAPI.overview) {
|
||||||
@ -187,17 +224,14 @@
|
|||||||
.then(function () {
|
.then(function () {
|
||||||
return userAPI.overview();
|
return userAPI.overview();
|
||||||
})
|
})
|
||||||
.then(applyCurrentUser);
|
.then(applyCurrentUser)
|
||||||
|
.then(loadMeProfile);
|
||||||
}
|
}
|
||||||
if (userAPI.me) {
|
if (userAPI.me) {
|
||||||
return userPromise
|
return userPromise.then(loadMeProfile);
|
||||||
.then(function () {
|
|
||||||
return userAPI.me();
|
|
||||||
})
|
|
||||||
.then(applyCurrentUser);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return userPromise;
|
return userPromise.then(loadMeProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toast(message) {
|
function toast(message) {
|
||||||
@ -583,6 +617,20 @@
|
|||||||
state.inviteRewardStatus = normalizeInviteRewardStatus(status);
|
state.inviteRewardStatus = normalizeInviteRewardStatus(status);
|
||||||
var current = state.inviteRewardStatus || {};
|
var current = state.inviteRewardStatus || {};
|
||||||
var progress = field(current, 'progress', 'progress') || {};
|
var progress = field(current, 'progress', 'progress') || {};
|
||||||
|
var perInviteReward = numberValue(
|
||||||
|
field(
|
||||||
|
current,
|
||||||
|
'per_invite_inviter_reward_coin_amount',
|
||||||
|
'perInviteInviterRewardCoinAmount'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (perInviteReward > 0) {
|
||||||
|
setText(
|
||||||
|
'#rewardInviteAmount',
|
||||||
|
'+' + formatNumber(perInviteReward) + '(Max40,000)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (state.inviteRewardStatus) {
|
if (state.inviteRewardStatus) {
|
||||||
setText(
|
setText(
|
||||||
@ -766,6 +814,247 @@
|
|||||||
copyTextFrom('#inviteCode');
|
copyTextFrom('#inviteCode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firstObjectValue(source, keys) {
|
||||||
|
source = source || {};
|
||||||
|
for (var i = 0; i < keys.length; i += 1) {
|
||||||
|
var value = source[keys[i]];
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBindModal() {
|
||||||
|
var api = window.HyAppAPI || {};
|
||||||
|
if (!api.getAccessToken || !api.getAccessToken()) {
|
||||||
|
toast(t('invite.loginRequired', 'Please log in first'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.pendingInviteCode = '';
|
||||||
|
state.searchedInviter = null;
|
||||||
|
state.searchingInviter = false;
|
||||||
|
state.bindingInviter = false;
|
||||||
|
var input = $('#bindInviteInput');
|
||||||
|
if (input) input.value = '';
|
||||||
|
showBindInput();
|
||||||
|
$('#bindInviteModal').hidden = false;
|
||||||
|
window.setTimeout(function () {
|
||||||
|
if (input) input.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBindModal() {
|
||||||
|
$('#bindInviteModal').hidden = true;
|
||||||
|
state.searchingInviter = false;
|
||||||
|
state.bindingInviter = false;
|
||||||
|
renderBindActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBindInput() {
|
||||||
|
$('#bindInputCard').hidden = false;
|
||||||
|
$('#bindResultCard').hidden = true;
|
||||||
|
state.searchedInviter = null;
|
||||||
|
renderBindActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBindResult(inviter) {
|
||||||
|
var avatar = $('#inviterAvatar');
|
||||||
|
var name = $('#inviterName');
|
||||||
|
var displayID = $('#inviterDisplayId');
|
||||||
|
if (avatar) {
|
||||||
|
avatar.src = inviter.avatar || './assets/rank-avatar.png';
|
||||||
|
avatar.onerror = function () {
|
||||||
|
avatar.onerror = null;
|
||||||
|
avatar.src = './assets/rank-avatar.png';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (name) name.textContent = inviter.name || '--';
|
||||||
|
if (displayID) displayID.textContent = inviter.displayUserID || '--';
|
||||||
|
$('#bindInputCard').hidden = true;
|
||||||
|
$('#bindResultCard').hidden = false;
|
||||||
|
renderBindActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBindActions() {
|
||||||
|
var cancel = $('#bindCancelButton');
|
||||||
|
var confirm = $('#confirmBindButton');
|
||||||
|
var input = $('#bindInviteInput');
|
||||||
|
var busy = state.searchingInviter || state.bindingInviter;
|
||||||
|
if (confirm) {
|
||||||
|
confirm.disabled = busy;
|
||||||
|
confirm.textContent = state.bindingInviter
|
||||||
|
? t('invite.binding', 'Binding...')
|
||||||
|
: state.searchingInviter
|
||||||
|
? t('invite.searchingInviter', 'Searching...')
|
||||||
|
: t('invite.confirm', 'Confirm');
|
||||||
|
}
|
||||||
|
if (cancel) cancel.disabled = busy;
|
||||||
|
if (input) input.disabled = busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeInviter(payload, fallbackDisplayUserID) {
|
||||||
|
payload = payload || {};
|
||||||
|
var wrapper = payload.inviter || payload.Inviter || payload;
|
||||||
|
var identity = payload.identity || payload.Identity || payload;
|
||||||
|
var profile =
|
||||||
|
payload.profile ||
|
||||||
|
payload.Profile ||
|
||||||
|
wrapper.profile ||
|
||||||
|
wrapper.Profile ||
|
||||||
|
wrapper;
|
||||||
|
var userID =
|
||||||
|
textValue(firstObjectValue(profile, ['user_id', 'userId'])) ||
|
||||||
|
textValue(firstObjectValue(identity, ['user_id', 'userId']));
|
||||||
|
var displayUserID =
|
||||||
|
textValue(
|
||||||
|
firstObjectValue(profile, [
|
||||||
|
'pretty_display_user_id',
|
||||||
|
'prettyDisplayUserId',
|
||||||
|
'display_user_id',
|
||||||
|
'displayUserId',
|
||||||
|
])
|
||||||
|
) ||
|
||||||
|
textValue(
|
||||||
|
firstObjectValue(identity, [
|
||||||
|
'pretty_display_user_id',
|
||||||
|
'prettyDisplayUserId',
|
||||||
|
'display_user_id',
|
||||||
|
'displayUserId',
|
||||||
|
])
|
||||||
|
) ||
|
||||||
|
fallbackDisplayUserID;
|
||||||
|
var name =
|
||||||
|
textValue(
|
||||||
|
firstObjectValue(profile, ['username', 'nickname', 'name'])
|
||||||
|
) || displayUserID;
|
||||||
|
var avatar = textValue(
|
||||||
|
firstObjectValue(profile, ['avatar', 'avatar_url', 'avatarUrl'])
|
||||||
|
);
|
||||||
|
if (!userID && !displayUserID) return null;
|
||||||
|
return {
|
||||||
|
userID: userID,
|
||||||
|
displayUserID: displayUserID,
|
||||||
|
name: name,
|
||||||
|
avatar: avatar,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentUser(inviter) {
|
||||||
|
var current = state.currentUser || {};
|
||||||
|
var currentUserID = textValue(
|
||||||
|
firstObjectValue(current, ['user_id', 'userId'])
|
||||||
|
);
|
||||||
|
var currentDisplayID = textValue(
|
||||||
|
firstObjectValue(current, [
|
||||||
|
'pretty_display_user_id',
|
||||||
|
'prettyDisplayUserId',
|
||||||
|
'display_user_id',
|
||||||
|
'displayUserId',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return Boolean(
|
||||||
|
(currentUserID && currentUserID === inviter.userID) ||
|
||||||
|
(currentDisplayID && currentDisplayID === inviter.displayUserID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchInviter() {
|
||||||
|
if (state.searchingInviter) return;
|
||||||
|
var input = $('#bindInviteInput');
|
||||||
|
var inviteCode = normalizeInviteCodeInput(input && input.value);
|
||||||
|
var api = window.HyAppAPI && window.HyAppAPI.user;
|
||||||
|
if (input && input.value !== inviteCode) input.value = inviteCode;
|
||||||
|
if (!inviteCode) {
|
||||||
|
toast(
|
||||||
|
t('invite.searchRequired', 'Please enter an invitation code')
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!api || !api.searchInviteReferrer) {
|
||||||
|
toast(t('invite.bindFailed', 'Bind failed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.searchingInviter = true;
|
||||||
|
state.pendingInviteCode = inviteCode;
|
||||||
|
renderBindActions();
|
||||||
|
// 搜索入口只提交邀请码,由 server 解析归属并按当前用户国家过滤,前端不再用可被篡改的用户 ID 作为绑定入参。
|
||||||
|
api.searchInviteReferrer(inviteCode)
|
||||||
|
.then(function (payload) {
|
||||||
|
var inviter = normalizeInviter(payload, '');
|
||||||
|
state.searchingInviter = false;
|
||||||
|
if (!inviter) {
|
||||||
|
renderBindActions();
|
||||||
|
toast(t('invite.inviterNotFound', 'Inviter not found'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isCurrentUser(inviter)) {
|
||||||
|
renderBindActions();
|
||||||
|
toast(
|
||||||
|
t(
|
||||||
|
'invite.cannotBindSelf',
|
||||||
|
'You cannot bind yourself as inviter'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inviter.inviteCode = inviteCode;
|
||||||
|
state.searchedInviter = inviter;
|
||||||
|
showBindResult(inviter);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
state.searchingInviter = false;
|
||||||
|
renderBindActions();
|
||||||
|
toast(
|
||||||
|
(error && error.message) ||
|
||||||
|
t('invite.inviterNotFound', 'Inviter not found')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmBindInviter() {
|
||||||
|
if (state.bindingInviter) return;
|
||||||
|
var api = window.HyAppAPI && window.HyAppAPI.user;
|
||||||
|
var inviter = state.searchedInviter;
|
||||||
|
if (!inviter) {
|
||||||
|
searchInviter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!api || !api.bindInviteReferrer) {
|
||||||
|
toast(t('invite.bindFailed', 'Bind failed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.bindingInviter = true;
|
||||||
|
renderBindActions();
|
||||||
|
api.bindInviteReferrer(inviter.inviteCode || state.pendingInviteCode)
|
||||||
|
.then(function (response) {
|
||||||
|
var invite = response && response.invite;
|
||||||
|
state.bindingInviter = false;
|
||||||
|
renderBindActions();
|
||||||
|
if (invite && (invite.bound || invite.invite_code)) {
|
||||||
|
toast(t('invite.bindSuccess', 'Bound successfully'));
|
||||||
|
closeBindModal();
|
||||||
|
loadCurrentUser().catch(function () {});
|
||||||
|
loadInviteActivityReward();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast(
|
||||||
|
t(
|
||||||
|
'invite.bindNoChange',
|
||||||
|
'Invite referrer is already bound.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
state.bindingInviter = false;
|
||||||
|
renderBindActions();
|
||||||
|
toast(
|
||||||
|
(error && error.message) ||
|
||||||
|
t('invite.bindFailed', 'Bind failed')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
document.querySelectorAll('.task-tab').forEach(function (button) {
|
document.querySelectorAll('.task-tab').forEach(function (button) {
|
||||||
button.addEventListener('click', function () {
|
button.addEventListener('click', function () {
|
||||||
@ -774,6 +1063,20 @@
|
|||||||
});
|
});
|
||||||
$('#copyLinkButton').addEventListener('click', copyInviteLink);
|
$('#copyLinkButton').addEventListener('click', copyInviteLink);
|
||||||
$('#copyCodeButton').addEventListener('click', copyInviteCode);
|
$('#copyCodeButton').addEventListener('click', copyInviteCode);
|
||||||
|
$('#bindInviteButton').addEventListener('click', openBindModal);
|
||||||
|
$('#bindCancelButton').addEventListener('click', closeBindModal);
|
||||||
|
$('#confirmBindButton').addEventListener('click', confirmBindInviter);
|
||||||
|
$('#bindInviteModal').addEventListener('click', function (event) {
|
||||||
|
if (event.target === $('#bindInviteModal')) closeBindModal();
|
||||||
|
});
|
||||||
|
$('#bindInviteInput').addEventListener('keydown', function (event) {
|
||||||
|
if (event.key === 'Enter') confirmBindInviter();
|
||||||
|
});
|
||||||
|
$('#bindInviteInput').addEventListener('input', function (event) {
|
||||||
|
var nextValue = normalizeInviteCodeInput(event.target.value);
|
||||||
|
if (event.target.value !== nextValue)
|
||||||
|
event.target.value = nextValue;
|
||||||
|
});
|
||||||
document
|
document
|
||||||
.querySelectorAll('.copy-task-button')
|
.querySelectorAll('.copy-task-button')
|
||||||
.forEach(function (button) {
|
.forEach(function (button) {
|
||||||
@ -798,6 +1101,7 @@
|
|||||||
|
|
||||||
window.addEventListener('hyapp:i18n-ready', function () {
|
window.addEventListener('hyapp:i18n-ready', function () {
|
||||||
renderRank();
|
renderRank();
|
||||||
|
renderBindActions();
|
||||||
renderInviteActivityReward(state.inviteRewardStatus);
|
renderInviteActivityReward(state.inviteRewardStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -171,18 +171,8 @@ button {
|
|||||||
width: calc(1016 * var(--u));
|
width: calc(1016 * var(--u));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-image:
|
background-position: center;
|
||||||
url('./assets/panel-top-a.png'), url('./assets/panel-bottom-a.png'),
|
background-size: 100% 100%;
|
||||||
url('./assets/panel-middle-a.png');
|
|
||||||
background-repeat: no-repeat, no-repeat, no-repeat;
|
|
||||||
background-position:
|
|
||||||
top center,
|
|
||||||
bottom center,
|
|
||||||
center center;
|
|
||||||
background-size:
|
|
||||||
100% auto,
|
|
||||||
100% auto,
|
|
||||||
100% 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ornate-panel > * {
|
.ornate-panel > * {
|
||||||
@ -211,9 +201,9 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reward-panel {
|
.reward-panel {
|
||||||
height: calc(790 * var(--u));
|
height: calc(770 * var(--u));
|
||||||
margin-top: calc(20 * var(--u));
|
margin-top: calc(20 * var(--u));
|
||||||
padding-top: calc(122 * var(--u));
|
padding-top: calc(104 * var(--u));
|
||||||
background-image: url('./assets/reward-panel-bg.png');
|
background-image: url('./assets/reward-panel-bg.png');
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@ -232,73 +222,75 @@ p {
|
|||||||
.reward-grid {
|
.reward-grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(1016 * var(--u));
|
width: calc(1016 * var(--u));
|
||||||
height: calc(486 * var(--u));
|
height: calc(450 * var(--u));
|
||||||
margin: calc(28 * var(--u)) auto 0;
|
margin: calc(34 * var(--u)) auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-item {
|
.reward-item {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: calc(486 * var(--u));
|
width: calc(420 * var(--u));
|
||||||
height: calc(486 * var(--u));
|
height: calc(450 * var(--u));
|
||||||
color: var(--gold);
|
color: #ffd133;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-item:first-child {
|
.reward-item-success {
|
||||||
left: calc(8 * var(--u));
|
left: calc(54 * var(--u));
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-item:last-child {
|
.reward-item-topup {
|
||||||
left: calc(522 * var(--u));
|
left: calc(560 * var(--u));
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-orb {
|
.reward-visual {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
display: block;
|
display: block;
|
||||||
inset: 0;
|
left: 50%;
|
||||||
width: calc(486 * var(--u));
|
top: 0;
|
||||||
height: calc(486 * var(--u));
|
width: calc(370 * var(--u));
|
||||||
|
max-width: none;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-gold {
|
.reward-item-topup .reward-visual {
|
||||||
position: absolute;
|
width: calc(382 * var(--u));
|
||||||
z-index: 1;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: calc(178 * var(--u));
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-item strong {
|
.reward-item strong {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(98 * var(--u));
|
top: calc(338 * var(--u));
|
||||||
left: calc(290 * var(--u));
|
left: 50%;
|
||||||
font-size: calc(40 * var(--u));
|
display: block;
|
||||||
|
width: calc(520 * var(--u));
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-size: calc(42 * var(--u));
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-item span {
|
.reward-item span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(421 * var(--u));
|
top: calc(404 * var(--u));
|
||||||
left: 50%;
|
left: 50%;
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(360 * var(--u));
|
width: calc(430 * var(--u));
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
color: var(--gold);
|
color: var(--white);
|
||||||
font-size: calc(30 * var(--u));
|
font-size: calc(34 * var(--u));
|
||||||
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plus {
|
.plus {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(207 * var(--u));
|
top: calc(142 * var(--u));
|
||||||
left: calc(472 * var(--u));
|
left: calc(472 * var(--u));
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(71 * var(--u));
|
width: calc(78 * var(--u));
|
||||||
height: calc(71 * var(--u));
|
height: calc(78 * var(--u));
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-copy {
|
.invite-copy {
|
||||||
@ -319,25 +311,35 @@ p {
|
|||||||
|
|
||||||
.invite-fields {
|
.invite-fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) calc(286 * var(--u));
|
gap: calc(34 * var(--u));
|
||||||
gap: calc(24 * var(--u));
|
width: calc(1016 * var(--u));
|
||||||
width: calc(760 * var(--u));
|
|
||||||
margin: calc(58 * var(--u)) auto 0;
|
margin: calc(58 * var(--u)) auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invite-field {
|
||||||
|
position: relative;
|
||||||
|
height: calc(93 * var(--u));
|
||||||
|
background: url('./assets/link-input.svg') center / 100% 100% no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
.link-box {
|
.link-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: calc(85 * var(--u));
|
height: 100%;
|
||||||
padding: 0 calc(28 * var(--u));
|
padding: 0 calc(112 * var(--u)) 0 calc(52 * var(--u));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
font-size: calc(30 * var(--u));
|
font-size: calc(30 * var(--u));
|
||||||
|
font-weight: 700;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background: url('./assets/link-input.svg') center / 100% 100% no-repeat;
|
}
|
||||||
|
|
||||||
|
.invite-field.is-code .link-box {
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: calc(112 * var(--u));
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-code {
|
.invite-code {
|
||||||
@ -345,13 +347,20 @@ p {
|
|||||||
letter-spacing: calc(1 * var(--u));
|
letter-spacing: calc(1 * var(--u));
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-actions {
|
.copy-icon-button {
|
||||||
display: flex;
|
position: absolute;
|
||||||
width: calc(764 * var(--u));
|
top: 50%;
|
||||||
align-items: center;
|
right: calc(42 * var(--u));
|
||||||
justify-content: center;
|
width: calc(52 * var(--u));
|
||||||
gap: calc(24 * var(--u));
|
height: calc(52 * var(--u));
|
||||||
margin: calc(50 * var(--u)) auto 0;
|
cursor: pointer;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: url('./assets/generated/copy-icon.png') center / 100% 100%
|
||||||
|
no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-icon-button:active {
|
||||||
|
transform: translateY(-50%) scale(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ornate-button {
|
.ornate-button {
|
||||||
@ -374,19 +383,8 @@ p {
|
|||||||
transform: translateY(calc(2 * var(--u)));
|
transform: translateY(calc(2 * var(--u)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-actions .ornate-button {
|
.bind-invite-button {
|
||||||
flex: 0 0 calc(520 * var(--u));
|
margin-top: calc(68 * var(--u));
|
||||||
width: calc(520 * var(--u));
|
|
||||||
height: calc(100 * var(--u));
|
|
||||||
font-size: calc(42 * var(--u));
|
|
||||||
}
|
|
||||||
|
|
||||||
.invite-actions .small-copy-button {
|
|
||||||
flex: 0 0 calc(220 * var(--u));
|
|
||||||
width: calc(220 * var(--u));
|
|
||||||
height: calc(78 * var(--u));
|
|
||||||
font-size: calc(30 * var(--u));
|
|
||||||
background-image: url('./assets/claim-active.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-panel {
|
.stats-panel {
|
||||||
@ -397,6 +395,7 @@ p {
|
|||||||
height: calc(520 * var(--u));
|
height: calc(520 * var(--u));
|
||||||
margin-top: calc(62 * var(--u));
|
margin-top: calc(62 * var(--u));
|
||||||
padding: calc(108 * var(--u)) calc(92 * var(--u)) calc(92 * var(--u));
|
padding: calc(108 * var(--u)) calc(92 * var(--u)) calc(92 * var(--u));
|
||||||
|
background-image: url('./assets/generated/panel-a-stats-bg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat {
|
.stat {
|
||||||
@ -428,10 +427,12 @@ p {
|
|||||||
margin-top: calc(34 * var(--u));
|
margin-top: calc(34 * var(--u));
|
||||||
margin-bottom: calc(32 * var(--u));
|
margin-bottom: calc(32 * var(--u));
|
||||||
padding: calc(126 * var(--u)) calc(78 * var(--u)) calc(70 * var(--u));
|
padding: calc(126 * var(--u)) calc(78 * var(--u)) calc(70 * var(--u));
|
||||||
|
background-image: url('./assets/generated/panel-a-monthly-bg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.monthly-panel.is-quota {
|
.monthly-panel.is-quota {
|
||||||
min-height: calc(1375 * var(--u));
|
min-height: calc(1375 * var(--u));
|
||||||
|
background-image: url('./assets/generated/panel-a-monthly-quota-bg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.monthly-panel h2,
|
.monthly-panel h2,
|
||||||
@ -610,9 +611,7 @@ p {
|
|||||||
margin-top: calc(42 * var(--u));
|
margin-top: calc(42 * var(--u));
|
||||||
margin-bottom: calc(28 * var(--u));
|
margin-bottom: calc(28 * var(--u));
|
||||||
padding: calc(126 * var(--u)) calc(68 * var(--u)) calc(72 * var(--u));
|
padding: calc(126 * var(--u)) calc(68 * var(--u)) calc(72 * var(--u));
|
||||||
background-image:
|
background-image: url('./assets/generated/panel-b-rank-bg.png');
|
||||||
url('./assets/panel-top-b.png'), url('./assets/panel-bottom-b.png'),
|
|
||||||
url('./assets/panel-middle-b.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rank-list {
|
.rank-list {
|
||||||
@ -687,6 +686,156 @@ p {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invite-modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: calc(28 * var(--u));
|
||||||
|
background: rgba(0, 0, 0, 0.68);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-card {
|
||||||
|
position: relative;
|
||||||
|
width: min(calc(1016 * var(--u)), calc(100vw - 24px));
|
||||||
|
min-height: calc(786 * var(--u));
|
||||||
|
padding: calc(104 * var(--u)) calc(90 * var(--u)) calc(78 * var(--u));
|
||||||
|
overflow: hidden;
|
||||||
|
background: url('./assets/generated/modal-bg.png') center / 100% 100%
|
||||||
|
no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-card > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-card h2 {
|
||||||
|
color: var(--gold);
|
||||||
|
font-size: calc(60 * var(--u));
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 calc(2 * var(--u)) calc(4 * var(--u)) rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-input-state,
|
||||||
|
.bind-result-state {
|
||||||
|
width: calc(827 * var(--u));
|
||||||
|
max-width: 100%;
|
||||||
|
margin: calc(70 * var(--u)) auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-input-shell {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: calc(93 * var(--u));
|
||||||
|
background: url('./assets/generated/invite-input-bg.png') center / 100% 100%
|
||||||
|
no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-input-shell input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 calc(124 * var(--u)) 0 calc(36 * var(--u));
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
800 calc(30 * var(--u)) / 1 'Source Han Sans SC',
|
||||||
|
'Noto Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
Arial,
|
||||||
|
sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
background: url('./assets/generated/search-icon.png') right
|
||||||
|
calc(44 * var(--u)) center / calc(52 * var(--u)) calc(49 * var(--u))
|
||||||
|
no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-input-shell input::placeholder {
|
||||||
|
color: rgba(246, 224, 178, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inviter-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: calc(122 * var(--u)) minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(28 * var(--u));
|
||||||
|
height: calc(152 * var(--u));
|
||||||
|
padding: 0 calc(42 * var(--u));
|
||||||
|
background: url('./assets/generated/inviter-card-bg.png') center / 100% 100%
|
||||||
|
no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inviter-card img {
|
||||||
|
width: calc(104 * var(--u));
|
||||||
|
height: calc(104 * var(--u));
|
||||||
|
border: calc(2 * var(--u)) solid #ffebbb;
|
||||||
|
border-radius: 999px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inviter-card strong,
|
||||||
|
.inviter-card span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inviter-card strong {
|
||||||
|
color: var(--white);
|
||||||
|
font-size: calc(36 * var(--u));
|
||||||
|
line-height: 1.1;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inviter-card span {
|
||||||
|
margin-top: calc(14 * var(--u));
|
||||||
|
color: var(--gold);
|
||||||
|
font-size: calc(28 * var(--u));
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, calc(441 * var(--u)));
|
||||||
|
justify-content: center;
|
||||||
|
gap: calc(38 * var(--u));
|
||||||
|
margin-top: calc(152 * var(--u));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-action-button {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(120 * var(--u));
|
||||||
|
color: #6f2d12;
|
||||||
|
font-size: calc(34 * var(--u));
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 calc(1 * var(--u)) calc(1 * var(--u))
|
||||||
|
rgba(255, 255, 255, 0.45);
|
||||||
|
background: url('./assets/generated/modal-button-active.png') center / 100%
|
||||||
|
100% no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-cancel-button {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
text-shadow: 0 calc(2 * var(--u)) calc(2 * var(--u)) rgba(0, 0, 0, 0.28);
|
||||||
|
background-image: url('./assets/generated/modal-button-inactive.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-action-button:disabled {
|
||||||
|
cursor: default;
|
||||||
|
filter: grayscale(0.25) brightness(0.86);
|
||||||
|
}
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -541,6 +541,12 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
body: payload || {},
|
body: payload || {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
completeOnboarding: function (payload) {
|
||||||
|
return request('/api/v1/users/me/onboarding/complete', {
|
||||||
|
method: 'POST',
|
||||||
|
body: payload || {},
|
||||||
|
});
|
||||||
|
},
|
||||||
resolveDisplayUserID: function (displayUserID) {
|
resolveDisplayUserID: function (displayUserID) {
|
||||||
return request(
|
return request(
|
||||||
'/api/v1/users/by-display-user-id/' +
|
'/api/v1/users/by-display-user-id/' +
|
||||||
@ -550,6 +556,18 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
searchInviteReferrer: function (inviteCode) {
|
||||||
|
return request('/api/v1/users/me/invite-referrer/search', {
|
||||||
|
method: 'GET',
|
||||||
|
query: { invite_code: inviteCode || '' },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bindInviteReferrer: function (inviteCode) {
|
||||||
|
return request('/api/v1/users/me/invite-referrer/bind', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { invite_code: String(inviteCode || '') },
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var walletAPI = {
|
var walletAPI = {
|
||||||
|
|||||||
@ -133,6 +133,32 @@
|
|||||||
"invite.noRankData": "لا توجد بيانات ترتيب",
|
"invite.noRankData": "لا توجد بيانات ترتيب",
|
||||||
"invite.copied": "تم النسخ",
|
"invite.copied": "تم النسخ",
|
||||||
"invite.copyFailed": "فشل النسخ",
|
"invite.copyFailed": "فشل النسخ",
|
||||||
|
"invite.successfulInvite": "دعوة ناجحة",
|
||||||
|
"invite.friendTopUp": "شحن الصديق",
|
||||||
|
"invite.copyInviteLink": "نسخ رابط الدعوة",
|
||||||
|
"invite.copyInviteCode": "نسخ رمز الدعوة",
|
||||||
|
"invite.linkInviter": "ربط الداعي",
|
||||||
|
"invite.bindInviteTitle": "ربط الداعي",
|
||||||
|
"invite.bindInviteHint": "أدخل رمز الدعوة ثم أكد الداعي قبل الربط.",
|
||||||
|
"invite.bindInvitePlaceholder": "أدخل رمز الدعوة",
|
||||||
|
"invite.searchInviter": "بحث",
|
||||||
|
"invite.searchingInviter": "جار البحث...",
|
||||||
|
"invite.inviterFoundTitle": "تأكيد الداعي",
|
||||||
|
"invite.bindInviteConfirmHint": "يرجى التحقق من معلومات الداعي قبل الربط.",
|
||||||
|
"invite.confirmBind": "ربط",
|
||||||
|
"invite.cancel": "إلغاء",
|
||||||
|
"invite.confirm": "تأكيد",
|
||||||
|
"invite.binding": "جار الربط...",
|
||||||
|
"invite.back": "رجوع",
|
||||||
|
"invite.close": "إغلاق",
|
||||||
|
"invite.inviterNotFound": "لم يتم العثور على الداعي",
|
||||||
|
"invite.loginRequired": "يرجى تسجيل الدخول أولا",
|
||||||
|
"invite.searchRequired": "أدخل رمز الدعوة",
|
||||||
|
"invite.cannotBindSelf": "لا يمكنك ربط نفسك كداع",
|
||||||
|
"invite.bindProfileRequired": "لا يمكن ربط هذا الحساب هنا.",
|
||||||
|
"invite.bindSuccess": "تم الربط بنجاح",
|
||||||
|
"invite.bindNoChange": "تم ربط الداعي بهذا الحساب بالفعل.",
|
||||||
|
"invite.bindFailed": "فشل الربط",
|
||||||
"host_center.title": "مركز المضيف",
|
"host_center.title": "مركز المضيف",
|
||||||
"host_center.back": "رجوع",
|
"host_center.back": "رجوع",
|
||||||
"host_center.change_language": "تغيير اللغة",
|
"host_center.change_language": "تغيير اللغة",
|
||||||
|
|||||||
@ -133,6 +133,32 @@
|
|||||||
"invite.noRankData": "No rank data",
|
"invite.noRankData": "No rank data",
|
||||||
"invite.copied": "Copied",
|
"invite.copied": "Copied",
|
||||||
"invite.copyFailed": "Copy failed",
|
"invite.copyFailed": "Copy failed",
|
||||||
|
"invite.successfulInvite": "Successful Invite",
|
||||||
|
"invite.friendTopUp": "Friend's Top-Up",
|
||||||
|
"invite.copyInviteLink": "Copy invitation link",
|
||||||
|
"invite.copyInviteCode": "Copy invitation code",
|
||||||
|
"invite.linkInviter": "Link Inviter",
|
||||||
|
"invite.bindInviteTitle": "Bind Referrer",
|
||||||
|
"invite.bindInviteHint": "Enter an invitation code and confirm the referrer before binding.",
|
||||||
|
"invite.bindInvitePlaceholder": "Enter Invitation Code",
|
||||||
|
"invite.searchInviter": "Search",
|
||||||
|
"invite.searchingInviter": "Searching...",
|
||||||
|
"invite.inviterFoundTitle": "Confirm inviter",
|
||||||
|
"invite.bindInviteConfirmHint": "Please check the inviter information before binding.",
|
||||||
|
"invite.confirmBind": "Bind",
|
||||||
|
"invite.cancel": "Cancel",
|
||||||
|
"invite.confirm": "Confirm",
|
||||||
|
"invite.binding": "Binding...",
|
||||||
|
"invite.back": "Back",
|
||||||
|
"invite.close": "Close",
|
||||||
|
"invite.inviterNotFound": "Inviter not found",
|
||||||
|
"invite.loginRequired": "Please log in first",
|
||||||
|
"invite.searchRequired": "Please enter an invitation code",
|
||||||
|
"invite.cannotBindSelf": "You cannot bind yourself as inviter",
|
||||||
|
"invite.bindProfileRequired": "Current account cannot bind here.",
|
||||||
|
"invite.bindSuccess": "Bound successfully",
|
||||||
|
"invite.bindNoChange": "Invite referrer is already bound.",
|
||||||
|
"invite.bindFailed": "Bind failed",
|
||||||
"host_center.title": "Host Center",
|
"host_center.title": "Host Center",
|
||||||
"host_center.back": "Back",
|
"host_center.back": "Back",
|
||||||
"host_center.change_language": "Change language",
|
"host_center.change_language": "Change language",
|
||||||
|
|||||||
@ -133,6 +133,32 @@
|
|||||||
"invite.noRankData": "No hay datos de ranking",
|
"invite.noRankData": "No hay datos de ranking",
|
||||||
"invite.copied": "Copiado",
|
"invite.copied": "Copiado",
|
||||||
"invite.copyFailed": "Error al copiar",
|
"invite.copyFailed": "Error al copiar",
|
||||||
|
"invite.successfulInvite": "Invitación exitosa",
|
||||||
|
"invite.friendTopUp": "Recarga del amigo",
|
||||||
|
"invite.copyInviteLink": "Copiar enlace de invitación",
|
||||||
|
"invite.copyInviteCode": "Copiar código de invitación",
|
||||||
|
"invite.linkInviter": "Vincular invitador",
|
||||||
|
"invite.bindInviteTitle": "Vincular invitador",
|
||||||
|
"invite.bindInviteHint": "Ingresa el código de invitación y confirma el invitador antes de vincular.",
|
||||||
|
"invite.bindInvitePlaceholder": "Ingresa el código de invitación",
|
||||||
|
"invite.searchInviter": "Buscar",
|
||||||
|
"invite.searchingInviter": "Buscando...",
|
||||||
|
"invite.inviterFoundTitle": "Confirmar invitador",
|
||||||
|
"invite.bindInviteConfirmHint": "Verifica la información del invitador antes de vincular.",
|
||||||
|
"invite.confirmBind": "Vincular",
|
||||||
|
"invite.cancel": "Cancelar",
|
||||||
|
"invite.confirm": "Confirmar",
|
||||||
|
"invite.binding": "Vinculando...",
|
||||||
|
"invite.back": "Volver",
|
||||||
|
"invite.close": "Cerrar",
|
||||||
|
"invite.inviterNotFound": "Invitador no encontrado",
|
||||||
|
"invite.loginRequired": "Inicia sesión primero",
|
||||||
|
"invite.searchRequired": "Ingresa un código de invitación",
|
||||||
|
"invite.cannotBindSelf": "No puedes vincularte como invitador",
|
||||||
|
"invite.bindProfileRequired": "Esta cuenta no puede vincular aquí.",
|
||||||
|
"invite.bindSuccess": "Vinculado correctamente",
|
||||||
|
"invite.bindNoChange": "Esta cuenta ya tiene un invitador vinculado.",
|
||||||
|
"invite.bindFailed": "Error al vincular",
|
||||||
"host_center.title": "Centro de anfitrión",
|
"host_center.title": "Centro de anfitrión",
|
||||||
"host_center.back": "Volver",
|
"host_center.back": "Volver",
|
||||||
"host_center.change_language": "Cambiar idioma",
|
"host_center.change_language": "Cambiar idioma",
|
||||||
|
|||||||
@ -100,6 +100,32 @@
|
|||||||
"invite.noRankData": "Belum ada data peringkat",
|
"invite.noRankData": "Belum ada data peringkat",
|
||||||
"invite.copied": "Disalin",
|
"invite.copied": "Disalin",
|
||||||
"invite.copyFailed": "Gagal menyalin",
|
"invite.copyFailed": "Gagal menyalin",
|
||||||
|
"invite.successfulInvite": "Undangan Berhasil",
|
||||||
|
"invite.friendTopUp": "Top up teman",
|
||||||
|
"invite.copyInviteLink": "Salin tautan undangan",
|
||||||
|
"invite.copyInviteCode": "Salin kode undangan",
|
||||||
|
"invite.linkInviter": "Tautkan pengundang",
|
||||||
|
"invite.bindInviteTitle": "Tautkan pengundang",
|
||||||
|
"invite.bindInviteHint": "Masukkan kode undangan, lalu konfirmasi pengundang sebelum menautkan.",
|
||||||
|
"invite.bindInvitePlaceholder": "Masukkan kode undangan",
|
||||||
|
"invite.searchInviter": "Cari",
|
||||||
|
"invite.searchingInviter": "Mencari...",
|
||||||
|
"invite.inviterFoundTitle": "Konfirmasi pengundang",
|
||||||
|
"invite.bindInviteConfirmHint": "Periksa informasi pengundang sebelum menautkan.",
|
||||||
|
"invite.confirmBind": "Tautkan",
|
||||||
|
"invite.cancel": "Batal",
|
||||||
|
"invite.confirm": "Konfirmasi",
|
||||||
|
"invite.binding": "Menautkan...",
|
||||||
|
"invite.back": "Kembali",
|
||||||
|
"invite.close": "Tutup",
|
||||||
|
"invite.inviterNotFound": "Pengundang tidak ditemukan",
|
||||||
|
"invite.loginRequired": "Silakan masuk terlebih dahulu",
|
||||||
|
"invite.searchRequired": "Masukkan kode undangan",
|
||||||
|
"invite.cannotBindSelf": "Kamu tidak bisa menautkan dirimu sebagai pengundang",
|
||||||
|
"invite.bindProfileRequired": "Akun ini tidak dapat ditautkan di sini.",
|
||||||
|
"invite.bindSuccess": "Berhasil ditautkan",
|
||||||
|
"invite.bindNoChange": "Akun ini sudah memiliki pengundang.",
|
||||||
|
"invite.bindFailed": "Gagal menautkan",
|
||||||
"host_center.title": "Pusat Host",
|
"host_center.title": "Pusat Host",
|
||||||
"host_center.back": "Kembali",
|
"host_center.back": "Kembali",
|
||||||
"host_center.change_language": "Ganti bahasa",
|
"host_center.change_language": "Ganti bahasa",
|
||||||
|
|||||||
@ -100,6 +100,32 @@
|
|||||||
"invite.noRankData": "Sıralama verisi yok",
|
"invite.noRankData": "Sıralama verisi yok",
|
||||||
"invite.copied": "Kopyalandı",
|
"invite.copied": "Kopyalandı",
|
||||||
"invite.copyFailed": "Kopyalama başarısız",
|
"invite.copyFailed": "Kopyalama başarısız",
|
||||||
|
"invite.successfulInvite": "Başarılı Davet",
|
||||||
|
"invite.friendTopUp": "Arkadaş yüklemesi",
|
||||||
|
"invite.copyInviteLink": "Davet bağlantısını kopyala",
|
||||||
|
"invite.copyInviteCode": "Davet kodunu kopyala",
|
||||||
|
"invite.linkInviter": "Davetçiyi bağla",
|
||||||
|
"invite.bindInviteTitle": "Davetçiyi bağla",
|
||||||
|
"invite.bindInviteHint": "Davet kodunu gir ve bağlamadan önce davetçiyi onayla.",
|
||||||
|
"invite.bindInvitePlaceholder": "Davet kodu gir",
|
||||||
|
"invite.searchInviter": "Ara",
|
||||||
|
"invite.searchingInviter": "Aranıyor...",
|
||||||
|
"invite.inviterFoundTitle": "Davetçiyi onayla",
|
||||||
|
"invite.bindInviteConfirmHint": "Bağlamadan önce davetçi bilgisini kontrol et.",
|
||||||
|
"invite.confirmBind": "Bağla",
|
||||||
|
"invite.cancel": "İptal",
|
||||||
|
"invite.confirm": "Onayla",
|
||||||
|
"invite.binding": "Bağlanıyor...",
|
||||||
|
"invite.back": "Geri",
|
||||||
|
"invite.close": "Kapat",
|
||||||
|
"invite.inviterNotFound": "Davetçi bulunamadı",
|
||||||
|
"invite.loginRequired": "Önce giriş yap",
|
||||||
|
"invite.searchRequired": "Davet kodu gir",
|
||||||
|
"invite.cannotBindSelf": "Kendini davetçi olarak bağlayamazsın",
|
||||||
|
"invite.bindProfileRequired": "Bu hesap burada bağlanamaz.",
|
||||||
|
"invite.bindSuccess": "Başarıyla bağlandı",
|
||||||
|
"invite.bindNoChange": "Bu hesap zaten bir davetçiye bağlı.",
|
||||||
|
"invite.bindFailed": "Bağlama başarısız",
|
||||||
"host_center.title": "Host Merkezi",
|
"host_center.title": "Host Merkezi",
|
||||||
"host_center.back": "Geri",
|
"host_center.back": "Geri",
|
||||||
"host_center.change_language": "Dili değiştir",
|
"host_center.change_language": "Dili değiştir",
|
||||||
|
|||||||
@ -100,6 +100,32 @@
|
|||||||
"invite.noRankData": "暂无排行榜数据",
|
"invite.noRankData": "暂无排行榜数据",
|
||||||
"invite.copied": "已复制",
|
"invite.copied": "已复制",
|
||||||
"invite.copyFailed": "复制失败",
|
"invite.copyFailed": "复制失败",
|
||||||
|
"invite.successfulInvite": "成功邀请",
|
||||||
|
"invite.friendTopUp": "好友充值",
|
||||||
|
"invite.copyInviteLink": "复制邀请链接",
|
||||||
|
"invite.copyInviteCode": "复制邀请码",
|
||||||
|
"invite.linkInviter": "绑定邀请人",
|
||||||
|
"invite.bindInviteTitle": "绑定邀请人",
|
||||||
|
"invite.bindInviteHint": "输入邀请码,确认邀请人后再绑定。",
|
||||||
|
"invite.bindInvitePlaceholder": "输入邀请码",
|
||||||
|
"invite.searchInviter": "搜索",
|
||||||
|
"invite.searchingInviter": "搜索中...",
|
||||||
|
"invite.inviterFoundTitle": "确认邀请人",
|
||||||
|
"invite.bindInviteConfirmHint": "绑定前请确认邀请人信息。",
|
||||||
|
"invite.confirmBind": "绑定",
|
||||||
|
"invite.cancel": "取消",
|
||||||
|
"invite.confirm": "确认",
|
||||||
|
"invite.binding": "绑定中...",
|
||||||
|
"invite.back": "返回",
|
||||||
|
"invite.close": "关闭",
|
||||||
|
"invite.inviterNotFound": "未找到邀请人",
|
||||||
|
"invite.loginRequired": "请先登录",
|
||||||
|
"invite.searchRequired": "请输入邀请码",
|
||||||
|
"invite.cannotBindSelf": "不能绑定自己为邀请人",
|
||||||
|
"invite.bindProfileRequired": "当前账号不能在这里绑定。",
|
||||||
|
"invite.bindSuccess": "绑定成功",
|
||||||
|
"invite.bindNoChange": "当前账号已绑定邀请人。",
|
||||||
|
"invite.bindFailed": "绑定失败",
|
||||||
"host_center.title": "主播中心",
|
"host_center.title": "主播中心",
|
||||||
"host_center.back": "返回",
|
"host_center.back": "返回",
|
||||||
"host_center.change_language": "切换语言",
|
"host_center.change_language": "切换语言",
|
||||||
|
|||||||