Implement registration invite code flow and admin invite codes

This commit is contained in:
2026-04-30 20:49:38 +08:00
parent 2aef81e51d
commit 42aab671ed
32 changed files with 1241 additions and 179 deletions

View File

@@ -47,7 +47,6 @@ import type {
ProfileReferralInviteCenterResponse,
ProfileSaveArchiveSummary,
ProfileWalletLedgerResponse,
RedeemProfileReferralInviteCodeResponse,
RedeemProfileRewardCodeResponse,
} from '../../../packages/shared/src/contracts/runtime';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
@@ -57,7 +56,6 @@ import { copyTextToClipboard } from '../../services/clipboard';
import {
getRpgProfileReferralInviteCenter,
getRpgProfileWalletLedger,
redeemRpgProfileReferralInviteCode,
redeemRpgProfileRewardCode,
} from '../../services/rpg-entry/rpgProfileClient';
import type { CustomWorldProfile } from '../../types';
@@ -144,7 +142,7 @@ const PLATFORM_HOME_TABS: PlatformHomeTab[] = [
const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024;
const AVATAR_OUTPUT_SIZE = 256;
const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
type ProfilePopupPanel = 'invite' | 'redeem' | 'community';
type ProfilePopupPanel = 'invite' | 'community';
type MobileHomeChannel = 'recommend' | 'today' | 'category';
type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';
@@ -1841,34 +1839,21 @@ function RewardCodeRedeemModal({
function ProfileReferralModal({
panel,
center,
inviteCodeInput,
isLoading,
isSubmitting,
error,
success,
onClose,
onInputChange,
onCopyInvite,
onSubmitRedeem,
}: {
panel: ProfilePopupPanel;
center: ProfileReferralInviteCenterResponse | null;
inviteCodeInput: string;
isLoading: boolean;
isSubmitting: boolean;
error: string | null;
success: string | null;
onClose: () => void;
onInputChange: (value: string) => void;
onCopyInvite: () => void;
onSubmitRedeem: () => void;
}) {
const title =
panel === 'invite'
? '邀请好友'
: panel === 'redeem'
? '填邀请码'
: '玩家社区';
const title = panel === 'invite' ? '邀请好友' : '玩家社区';
return (
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/42 px-3 py-5">
@@ -1911,7 +1896,7 @@ function ProfileReferralModal({
<div className="h-20 animate-pulse rounded-xl bg-zinc-100" />
<div className="h-10 animate-pulse rounded-xl bg-zinc-100" />
</div>
) : panel === 'invite' ? (
) : (
<div className="mt-5 space-y-3">
<div className="rounded-xl bg-zinc-50 px-4 py-4 text-center">
<div className="text-[11px] font-bold text-zinc-500">
@@ -1951,31 +1936,6 @@ function ProfileReferralModal({
</div>
</div>
</div>
) : (
<div className="mt-5 space-y-3">
{center?.hasRedeemedCode ? (
<div className="rounded-xl bg-emerald-50 px-4 py-4 text-center text-sm font-bold text-emerald-700">
</div>
) : (
<>
<input
value={inviteCodeInput}
onChange={(event) => onInputChange(event.target.value)}
placeholder="输入邀请码"
className="w-full rounded-xl border border-zinc-200 bg-zinc-50 px-4 py-3 text-center text-base font-black tracking-[0.14em] outline-none focus:border-[#ff4056]"
/>
<button
type="button"
onClick={onSubmitRedeem}
disabled={isSubmitting || !inviteCodeInput.trim()}
className="w-full rounded-xl bg-[#ff4056] px-4 py-3 text-sm font-black text-white disabled:opacity-60"
>
{isSubmitting ? '提交中' : '确认填写'}
</button>
</>
)}
</div>
)}
{error ? (
@@ -2153,10 +2113,8 @@ export function RpgEntryHomeView({
const [referralCenter, setReferralCenter] =
useState<ProfileReferralInviteCenterResponse | null>(null);
const [isLoadingReferral, setIsLoadingReferral] = useState(false);
const [isSubmittingReferral, setIsSubmittingReferral] = useState(false);
const [referralError, setReferralError] = useState<string | null>(null);
const [referralSuccess, setReferralSuccess] = useState<string | null>(null);
const [inviteCodeInput, setInviteCodeInput] = useState('');
const [selectedCategoryTag, setSelectedCategoryTag] = useState<string | null>(
null,
);
@@ -2519,30 +2477,6 @@ export function RpgEntryHomeView({
},
);
};
const submitReferralInviteCode = () => {
if (isSubmittingReferral || !inviteCodeInput.trim()) {
return;
}
setIsSubmittingReferral(true);
setReferralError(null);
setReferralSuccess(null);
void redeemRpgProfileReferralInviteCode(inviteCodeInput)
.then((response: RedeemProfileReferralInviteCodeResponse) => {
setReferralCenter(response.center);
setInviteCodeInput('');
setReferralSuccess(
response.inviteeRewardGranted ? '已获得30陶泥币' : '填写成功',
);
void onRechargeSuccess?.();
})
.catch((error: unknown) => {
setReferralError(
error instanceof Error ? error.message : '填写邀请码失败',
);
})
.finally(() => setIsSubmittingReferral(false));
};
const openRewardCodeModal = () => {
setIsRewardCodeOpen(true);
setRewardCodeError(null);
@@ -3061,17 +2995,12 @@ export function RpgEntryHomeView({
<section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
<SectionHeader title="常用功能" detail="快捷入口" />
<div className="grid grid-cols-3 gap-3">
<div className="grid grid-cols-2 gap-3">
<ProfileShortcutButton
label="邀请好友"
icon={UserPlus}
onClick={() => openProfilePopupPanel('invite')}
/>
<ProfileShortcutButton
label="填邀请码"
icon={Ticket}
onClick={() => openProfilePopupPanel('redeem')}
/>
<ProfileShortcutButton
label="玩家社区"
icon={MessageCircle}
@@ -3506,15 +3435,11 @@ export function RpgEntryHomeView({
<ProfileReferralModal
panel={profilePopupPanel}
center={referralCenter}
inviteCodeInput={inviteCodeInput}
isLoading={isLoadingReferral}
isSubmitting={isSubmittingReferral}
error={referralError}
success={referralSuccess}
onClose={() => setProfilePopupPanel(null)}
onInputChange={setInviteCodeInput}
onCopyInvite={copyInviteInfo}
onSubmitRedeem={submitReferralInviteCode}
/>
) : null}
{rewardCodeModal}
@@ -3628,15 +3553,11 @@ export function RpgEntryHomeView({
<ProfileReferralModal
panel={profilePopupPanel}
center={referralCenter}
inviteCodeInput={inviteCodeInput}
isLoading={isLoadingReferral}
isSubmitting={isSubmittingReferral}
error={referralError}
success={referralSuccess}
onClose={() => setProfilePopupPanel(null)}
onInputChange={setInviteCodeInput}
onCopyInvite={copyInviteInfo}
onSubmitRedeem={submitReferralInviteCode}
/>
) : null}
{isProfilePlayStatsOpen ? (