app邀请

This commit is contained in:
zhx 2026-06-30 10:27:21 +08:00
parent 909b70e63d
commit d4f2a88cec
26 changed files with 816 additions and 130 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -7,7 +7,7 @@
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>Invite Friends</title>
<link rel="stylesheet" href="./style.css?v=20260611-1" />
<link rel="stylesheet" href="./style.css?v=20260629-bind-invite" />
</head>
<body>
<main
@ -72,38 +72,28 @@
(Earn for every friend you invite)
</p>
<div class="reward-grid">
<article class="reward-item">
<article class="reward-item reward-item-success">
<img
class="reward-orb"
src="./assets/reward-orb-bg.png"
class="reward-visual"
src="./assets/generated/reward-success-visual.png"
alt=""
/>
<img
class="reward-gold"
src="./assets/gold-stack.png"
alt=""
/>
<strong>+2000</strong>
<span data-i18n="invite.invitationReceived">
Invitation received
<strong id="rewardInviteAmount"> +1000 </strong>
<span data-i18n="invite.successfulInvite">
Successful Invite
</span>
</article>
<img class="plus" src="./assets/plus.svg" alt="+" />
<article class="reward-item">
<article class="reward-item reward-item-topup">
<img
class="reward-orb"
src="./assets/reward-orb-bg.png"
class="reward-visual"
src="./assets/generated/reward-topup-visual.png"
alt=""
/>
<img
class="reward-gold"
src="./assets/gold-stack.png"
alt=""
/>
<strong>+1000</strong>
<span data-i18n="invite.topUpViaFriends">
Top up via friends
</span>
<strong id="rewardTopUpPercent">1%</strong>
<span data-i18n="invite.friendTopUp"
>Friend's Top-Up</span
>
</article>
</div>
</section>
@ -116,31 +106,39 @@
(Copy your invitation link and share it with your friends)
</p>
<div class="invite-fields">
<div class="link-box" id="inviteLink">
http://yourappbalbalanal
<div class="invite-field">
<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 class="link-box invite-code" id="inviteCode">
ABC123
<div class="invite-field is-code">
<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 class="invite-actions">
<button
class="ornate-button"
id="copyLinkButton"
type="button"
data-i18n="invite.copy"
>
Copy
</button>
<button
class="ornate-button small-copy-button"
id="copyCodeButton"
type="button"
data-i18n="invite.copy"
>
Copy
</button>
</div>
<button
class="ornate-button bind-invite-button"
id="bindInviteButton"
type="button"
data-i18n="invite.linkInviter"
>
Link Inviter
</button>
</section>
<section class="ornate-panel stats-panel" aria-label="Invite stats">
@ -234,6 +232,67 @@
<h2 data-i18n="invite.rank">Rank</h2>
<div class="rank-list" id="rankList"></div>
</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>
<script>
@ -253,9 +312,9 @@
})();
</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/toast.js"></script>
<script src="./script.js?v=20260611-invite-rank"></script>
<script src="./script.js?v=20260630-invite-code"></script>
</body>
</html>

View File

@ -26,10 +26,15 @@
inviterName: firstParam(SHARE_NAME_KEYS),
inviterAvatar: firstParam(SHARE_AVATAR_KEYS),
inviteLink: '',
currentUser: null,
pendingInviteCode: '',
searchedInviter: null,
inviteRewardStatus: null,
rankRows: [],
rankLoaded: false,
rankLoading: false,
searchingInviter: false,
bindingInviter: false,
claiming: {},
};
@ -49,6 +54,10 @@
return String(value || '').trim();
}
function normalizeInviteCodeInput(value) {
return textValue(value).toUpperCase();
}
function readPath(source, path) {
var value = source;
for (var i = 0; i < path.length; i += 1) {
@ -66,6 +75,25 @@
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() {
return new URL('../invite-landing/', window.location.href);
}
@ -112,6 +140,7 @@
function applyCurrentUser(payload) {
var data = readOverviewPayload(payload || {});
var profile = data.profile || data;
if (currentUserPayload(data, profile)) mergeCurrentUser(profile);
var inviteCode = firstPath(data, [
['invite', 'my_invite_code'],
['invite', 'myInviteCode'],
@ -171,6 +200,13 @@
.then(applyCurrentUser)
.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) {
return userPromise
@ -179,7 +215,8 @@
})
.then(function (current) {
if (current && current.user) applyCurrentUser(current.user);
});
})
.then(loadMeProfile);
}
if (userAPI) {
if (userAPI.overview) {
@ -187,17 +224,14 @@
.then(function () {
return userAPI.overview();
})
.then(applyCurrentUser);
.then(applyCurrentUser)
.then(loadMeProfile);
}
if (userAPI.me) {
return userPromise
.then(function () {
return userAPI.me();
})
.then(applyCurrentUser);
return userPromise.then(loadMeProfile);
}
}
return userPromise;
return userPromise.then(loadMeProfile);
}
function toast(message) {
@ -583,6 +617,20 @@
state.inviteRewardStatus = normalizeInviteRewardStatus(status);
var current = state.inviteRewardStatus || {};
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) {
setText(
@ -766,6 +814,247 @@
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() {
document.querySelectorAll('.task-tab').forEach(function (button) {
button.addEventListener('click', function () {
@ -774,6 +1063,20 @@
});
$('#copyLinkButton').addEventListener('click', copyInviteLink);
$('#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
.querySelectorAll('.copy-task-button')
.forEach(function (button) {
@ -798,6 +1101,7 @@
window.addEventListener('hyapp:i18n-ready', function () {
renderRank();
renderBindActions();
renderInviteActivityReward(state.inviteRewardStatus);
});

View File

@ -171,18 +171,8 @@ button {
width: calc(1016 * var(--u));
margin: 0 auto;
overflow: hidden;
background-image:
url('./assets/panel-top-a.png'), url('./assets/panel-bottom-a.png'),
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%;
background-position: center;
background-size: 100% 100%;
}
.ornate-panel > * {
@ -211,9 +201,9 @@ p {
}
.reward-panel {
height: calc(790 * var(--u));
height: calc(770 * 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-position: center;
background-size: 100% 100%;
@ -232,73 +222,75 @@ p {
.reward-grid {
position: relative;
width: calc(1016 * var(--u));
height: calc(486 * var(--u));
margin: calc(28 * var(--u)) auto 0;
height: calc(450 * var(--u));
margin: calc(34 * var(--u)) auto 0;
}
.reward-item {
position: absolute;
top: 0;
width: calc(486 * var(--u));
height: calc(486 * var(--u));
color: var(--gold);
width: calc(420 * var(--u));
height: calc(450 * var(--u));
color: #ffd133;
text-align: center;
}
.reward-item:first-child {
left: calc(8 * var(--u));
.reward-item-success {
left: calc(54 * var(--u));
}
.reward-item:last-child {
left: calc(522 * var(--u));
.reward-item-topup {
left: calc(560 * var(--u));
}
.reward-orb {
.reward-visual {
position: absolute;
z-index: 0;
display: block;
inset: 0;
width: calc(486 * var(--u));
height: calc(486 * var(--u));
left: 50%;
top: 0;
width: calc(370 * var(--u));
max-width: none;
transform: translateX(-50%);
object-fit: contain;
}
.reward-gold {
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
width: calc(178 * var(--u));
transform: translate(-50%, -50%);
.reward-item-topup .reward-visual {
width: calc(382 * var(--u));
}
.reward-item strong {
position: absolute;
top: calc(98 * var(--u));
left: calc(290 * var(--u));
font-size: calc(40 * var(--u));
top: calc(338 * var(--u));
left: 50%;
display: block;
width: calc(520 * var(--u));
transform: translateX(-50%);
font-size: calc(42 * var(--u));
line-height: 1;
white-space: nowrap;
}
.reward-item span {
position: absolute;
top: calc(421 * var(--u));
top: calc(404 * var(--u));
left: 50%;
display: block;
width: calc(360 * var(--u));
width: calc(430 * var(--u));
transform: translateX(-50%);
color: var(--gold);
font-size: calc(30 * var(--u));
color: var(--white);
font-size: calc(34 * var(--u));
font-weight: 700;
line-height: 1.2;
}
.plus {
position: absolute;
top: calc(207 * var(--u));
top: calc(142 * var(--u));
left: calc(472 * var(--u));
display: block;
width: calc(71 * var(--u));
height: calc(71 * var(--u));
width: calc(78 * var(--u));
height: calc(78 * var(--u));
}
.invite-copy {
@ -319,25 +311,35 @@ p {
.invite-fields {
display: grid;
grid-template-columns: minmax(0, 1fr) calc(286 * var(--u));
gap: calc(24 * var(--u));
width: calc(760 * var(--u));
gap: calc(34 * var(--u));
width: calc(1016 * var(--u));
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 {
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
min-width: 0;
height: calc(85 * var(--u));
padding: 0 calc(28 * var(--u));
height: 100%;
padding: 0 calc(112 * var(--u)) 0 calc(52 * var(--u));
overflow: hidden;
color: var(--gold);
font-size: calc(30 * var(--u));
font-weight: 700;
text-overflow: ellipsis;
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 {
@ -345,13 +347,20 @@ p {
letter-spacing: calc(1 * var(--u));
}
.invite-actions {
display: flex;
width: calc(764 * var(--u));
align-items: center;
justify-content: center;
gap: calc(24 * var(--u));
margin: calc(50 * var(--u)) auto 0;
.copy-icon-button {
position: absolute;
top: 50%;
right: calc(42 * var(--u));
width: calc(52 * var(--u));
height: calc(52 * var(--u));
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 {
@ -374,19 +383,8 @@ p {
transform: translateY(calc(2 * var(--u)));
}
.invite-actions .ornate-button {
flex: 0 0 calc(520 * 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');
.bind-invite-button {
margin-top: calc(68 * var(--u));
}
.stats-panel {
@ -397,6 +395,7 @@ p {
height: calc(520 * var(--u));
margin-top: calc(62 * 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 {
@ -428,10 +427,12 @@ p {
margin-top: calc(34 * var(--u));
margin-bottom: calc(32 * 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 {
min-height: calc(1375 * var(--u));
background-image: url('./assets/generated/panel-a-monthly-quota-bg.png');
}
.monthly-panel h2,
@ -610,9 +611,7 @@ p {
margin-top: calc(42 * var(--u));
margin-bottom: calc(28 * var(--u));
padding: calc(126 * var(--u)) calc(68 * var(--u)) calc(72 * var(--u));
background-image:
url('./assets/panel-top-b.png'), url('./assets/panel-bottom-b.png'),
url('./assets/panel-middle-b.png');
background-image: url('./assets/generated/panel-b-rank-bg.png');
}
.rank-list {
@ -687,6 +686,156 @@ p {
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] {
display: none !important;
}

View File

@ -541,6 +541,12 @@ var default_api = 'https://api.global-interaction.com/';
body: payload || {},
});
},
completeOnboarding: function (payload) {
return request('/api/v1/users/me/onboarding/complete', {
method: 'POST',
body: payload || {},
});
},
resolveDisplayUserID: function (displayUserID) {
return request(
'/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 = {

View File

@ -133,6 +133,32 @@
"invite.noRankData": "لا توجد بيانات ترتيب",
"invite.copied": "تم النسخ",
"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.back": "رجوع",
"host_center.change_language": "تغيير اللغة",

View File

@ -133,6 +133,32 @@
"invite.noRankData": "No rank data",
"invite.copied": "Copied",
"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.back": "Back",
"host_center.change_language": "Change language",

View File

@ -133,6 +133,32 @@
"invite.noRankData": "No hay datos de ranking",
"invite.copied": "Copiado",
"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.back": "Volver",
"host_center.change_language": "Cambiar idioma",

View File

@ -100,6 +100,32 @@
"invite.noRankData": "Belum ada data peringkat",
"invite.copied": "Disalin",
"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.back": "Kembali",
"host_center.change_language": "Ganti bahasa",

View File

@ -100,6 +100,32 @@
"invite.noRankData": "Sıralama verisi yok",
"invite.copied": "Kopyalandı",
"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.back": "Geri",
"host_center.change_language": "Dili değiştir",

View File

@ -100,6 +100,32 @@
"invite.noRankData": "暂无排行榜数据",
"invite.copied": "已复制",
"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.back": "返回",
"host_center.change_language": "切换语言",