feat: improve entry gate logic and lifecycle management
- Introduced a ref to manage entry lifecycle states, preventing duplicate entry attempts during loading and error handling. - Updated entry handling to ensure proper state transitions and avoid flickering on success or failure pages. - Enhanced token validation logic to streamline the entry process and improve user experience.
This commit is contained in:
@@ -12,7 +12,14 @@ import {
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { getPublicCurrencies } from "@/api/currency";
|
||||
@@ -151,6 +158,8 @@ export function EntryGate() {
|
||||
const [steps, setSteps] = useState<EntryStep[]>(initialSteps());
|
||||
|
||||
const effectiveToken = tokenFromUrl || bearerToken;
|
||||
/** 防止 token 写入 store / URL 剥离后重复触发进场,避免成功/失败页闪一下 */
|
||||
const entryLifecycleRef = useRef<"idle" | "running" | "done">("idle");
|
||||
|
||||
const updateStep = useCallback((stepId: EntryStepId, status: EntryStepStatus) => {
|
||||
setSteps((prev) => prev.map((s) => (s.id === stepId ? { ...s, status } : s)));
|
||||
@@ -163,6 +172,10 @@ export function EntryGate() {
|
||||
}, [steps]);
|
||||
|
||||
const doEntry = useCallback(async () => {
|
||||
if (entryLifecycleRef.current === "running" || entryLifecycleRef.current === "done") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!effectiveToken) {
|
||||
// 主站 iframe:token 由 MAIN_INIT_TOKEN 稍后到达,勿先闪「授权失败」
|
||||
if (typeof window !== "undefined" && isInIframe() && !tokenFromUrl) {
|
||||
@@ -172,11 +185,13 @@ export function EntryGate() {
|
||||
router.replace("/login");
|
||||
return;
|
||||
}
|
||||
entryLifecycleRef.current = "done";
|
||||
setPhase("failed");
|
||||
setFailureDetails([{ code: "NO_TOKEN", detailKey: "errors.noTokenDetail" }]);
|
||||
return;
|
||||
}
|
||||
|
||||
entryLifecycleRef.current = "running";
|
||||
setPhase("loading");
|
||||
setFailureDetails([]);
|
||||
|
||||
@@ -215,15 +230,14 @@ export function EntryGate() {
|
||||
|
||||
updateStep("hall", "done");
|
||||
setProfile(me);
|
||||
setPhase("success");
|
||||
|
||||
await sleep(600);
|
||||
entryLifecycleRef.current = "done";
|
||||
router.replace("/hall");
|
||||
return;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
|
||||
if (err instanceof LotteryApiBizError) {
|
||||
entryLifecycleRef.current = "done";
|
||||
updateStep("token", "error");
|
||||
setPhase("failed");
|
||||
|
||||
@@ -253,6 +267,7 @@ export function EntryGate() {
|
||||
}
|
||||
|
||||
if (!shouldRetryEntryRequest(err)) {
|
||||
entryLifecycleRef.current = "done";
|
||||
updateStep("token", "error");
|
||||
setPhase("failed");
|
||||
setFailureDetails([{ code: "NETWORK_ERROR", detailKey: "errors.networkDetail" }]);
|
||||
@@ -265,6 +280,7 @@ export function EntryGate() {
|
||||
}
|
||||
}
|
||||
|
||||
entryLifecycleRef.current = "done";
|
||||
updateStep("token", "error");
|
||||
setPhase("failed");
|
||||
setFailureDetails([
|
||||
@@ -287,6 +303,7 @@ export function EntryGate() {
|
||||
]);
|
||||
|
||||
const handleRetry = useCallback(() => {
|
||||
entryLifecycleRef.current = "idle";
|
||||
setPhase("loading");
|
||||
setFailureDetails([]);
|
||||
setSteps(initialSteps());
|
||||
@@ -299,13 +316,20 @@ export function EntryGate() {
|
||||
stripSearchParamFromBrowserUrl("session");
|
||||
}, [sessionExpired, clearBearerToken]);
|
||||
|
||||
const trimmedEffectiveToken = (effectiveToken ?? "").trim();
|
||||
const doEntryRef = useRef(doEntry);
|
||||
doEntryRef.current = doEntry;
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionExpired || waitingForEmbeddedToken) return;
|
||||
if (entryLifecycleRef.current !== "idle") return;
|
||||
if (!trimmedEffectiveToken) return;
|
||||
|
||||
const tmr = window.setTimeout(() => {
|
||||
void doEntry();
|
||||
void doEntryRef.current();
|
||||
}, 300);
|
||||
return () => window.clearTimeout(tmr);
|
||||
}, [doEntry, sessionExpired, waitingForEmbeddedToken]);
|
||||
}, [sessionExpired, trimmedEffectiveToken, waitingForEmbeddedToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionExpired) return;
|
||||
@@ -314,6 +338,8 @@ export function EntryGate() {
|
||||
|
||||
const tmr = window.setTimeout(() => {
|
||||
if (usePlayerSessionStore.getState().bearerToken?.trim()) return;
|
||||
if (entryLifecycleRef.current !== "idle") return;
|
||||
entryLifecycleRef.current = "done";
|
||||
setFailureDetails([{ code: "NO_TOKEN", detailKey: "errors.noTokenDetail" }]);
|
||||
setPhase("failed");
|
||||
}, IFRAME_TOKEN_WAIT_MS);
|
||||
|
||||
Reference in New Issue
Block a user