优化邀请码链接自动打开流程
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-07 17:09:20 +08:00
parent 89be59d701
commit 9146e5b8ec
3 changed files with 83 additions and 3 deletions

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ temp*build*/
/.codex-temp
/target/
/logs
.worktrees/

View File

@@ -692,6 +692,7 @@ afterEach(() => {
configurable: true,
value: undefined,
});
window.history.replaceState(null, '', '/');
});
test('opens wallet ledger modal from narrative coin card', async () => {
@@ -859,6 +860,37 @@ test('profile redeem invite shortcut hides after redeemed or one day old', async
).toBeNull();
});
test('invite query opens login modal for logged out users', async () => {
const openLoginModal = vi.fn();
window.history.replaceState(null, '', '/?inviteCode=spring-2026');
renderLoggedOutHomeView(openLoginModal);
await waitFor(() => {
expect(openLoginModal).toHaveBeenCalledTimes(1);
});
});
test('invite query opens redeem modal directly for logged in users', async () => {
window.history.replaceState(null, '', '/?inviteCode=spring-2026');
renderProfileView();
const input = await screen.findByLabelText('邀请码');
expect((input as HTMLInputElement).value).toBe('SPRING2026');
});
test('profile redeem invite modal reads query invite code after login', async () => {
window.history.replaceState(null, '', '/?inviteCode=spring-2026');
renderProfileView();
const input = await screen.findByLabelText('邀请码');
expect((input as HTMLInputElement).value).toBe('SPRING2026');
});
test('profile redeem invite modal submits code and hides shortcut after success', async () => {
const user = userEvent.setup();
const onRechargeSuccess = vi.fn();

View File

@@ -62,10 +62,10 @@ import {
} from '../../services/authService';
import { copyTextToClipboard } from '../../services/clipboard';
import {
claimRpgProfileTaskReward,
getRpgProfileReferralInviteCenter,
getRpgProfileTasks,
getRpgProfileWalletLedger,
claimRpgProfileTaskReward,
redeemRpgProfileReferralInviteCode,
redeemRpgProfileRewardCode,
} from '../../services/rpg-entry/rpgProfileClient';
@@ -158,6 +158,8 @@ const AVATAR_OUTPUT_SIZE = 256;
const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
const PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS = 4200;
const PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS = 24 * 60 * 60 * 1000;
const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const;
type ProfilePopupPanel = 'invite' | 'redeem' | 'community';
type MobileHomeChannel = 'recommend' | 'today' | 'category';
type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';
@@ -1514,6 +1516,24 @@ function isWithinProfileInviteRedeemWindow(
return Date.now() - createdTime <= PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS;
}
function normalizeProfileInviteQueryCode(value: string | null | undefined) {
return (value ?? '')
.trim()
.replace(/[^0-9a-z]/giu, '')
.toUpperCase();
}
function readProfileInviteCodeFromLocationSearch(search: string) {
const params = new URLSearchParams(search);
for (const key of PROFILE_INVITE_QUERY_KEYS) {
const inviteCode = normalizeProfileInviteQueryCode(params.get(key));
if (inviteCode) {
return inviteCode;
}
}
return '';
}
function formatPlayedWorkType(value: string | null | undefined) {
const normalizedValue = (value ?? '').toLowerCase();
if (normalizedValue === 'puzzle') {
@@ -2695,7 +2715,17 @@ export function RpgEntryHomeView({
const [isLoadingReferral, setIsLoadingReferral] = useState(false);
const [isReferralCenterInitialized, setIsReferralCenterInitialized] =
useState(false);
const [referralRedeemCode, setReferralRedeemCode] = useState('');
const pendingProfileInviteCode = useMemo(
() =>
typeof window === 'undefined'
? ''
: readProfileInviteCodeFromLocationSearch(window.location.search),
[],
);
const autoOpenedInviteQueryRef = useRef(false);
const [referralRedeemCode, setReferralRedeemCode] = useState(
pendingProfileInviteCode,
);
const [isSubmittingReferralRedeem, setIsSubmittingReferralRedeem] =
useState(false);
const [referralError, setReferralError] = useState<string | null>(null);
@@ -2936,6 +2966,23 @@ export function RpgEntryHomeView({
}
authUi?.openLoginModal();
};
useEffect(() => {
if (!pendingProfileInviteCode || autoOpenedInviteQueryRef.current) {
return;
}
autoOpenedInviteQueryRef.current = true;
if (!authUi?.user) {
authUi?.openLoginModal();
return;
}
setReferralRedeemCode(pendingProfileInviteCode);
setReferralError(null);
setReferralSuccess(null);
setProfilePopupPanel('redeem');
}, [authUi, pendingProfileInviteCode]);
const scheduleProfileCopyStateReset = () => {
if (profileCopyResetTimerRef.current !== null) {
window.clearTimeout(profileCopyResetTimerRef.current);
@@ -3173,7 +3220,7 @@ export function RpgEntryHomeView({
setReferralError(null);
setReferralSuccess(null);
if (panel === 'redeem') {
setReferralRedeemCode('');
setReferralRedeemCode(pendingProfileInviteCode);
}
if (panel === 'community') {
return;