Files
cozsweet-frontend-nextjs/src/app/splash/components/splash-screen.tsx
T
admin c07a10ee80 refactor(splash): handle Skip redirect in splash screen, decouple SplashButton
Previously, the Skip button (guest login entry) relied on the auth
state machine's `pendingRedirect` flag to trigger the navigation
to /chat after guest login completed. That introduced a
non-obvious coupling: the button dispatched an event, the machine
ran an actor, the auth screen (or splash useEffect) saw the flag
and redirected.

This refactor moves the redirect into the splash screen itself:

- `SplashButton` now takes an `onSkip` prop and no longer knows
  about auth dispatch. Pure presentation component.
- `SplashScreen` provides `handleSkip` that dispatches the
  `AuthGuestLoginSubmitted` event AND immediately calls
  `router.replace(/chat)` for snappier perceived navigation.
- `auth-machine.ts`: `loadingGuestLogin.onDone` no longer sets
  `pendingRedirect: true` (splash already navigated). Sets
  it to `false` explicitly so other screens (auth, sidebar) that
  also react to `pendingRedirect` don't double-navigate if the
  user triggers guest login from a non-splash surface in the future.

No behavior change for the happy path: Skip → /chat works the same.
The refactor is purely about responsibility allocation and
component decoupling.
2026-06-17 11:51:35 +08:00

126 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core/mobile-shell";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { useUserDispatch } from "@/stores/user/user-context";
import { AuthStorage } from "@/data/storage/auth/auth_storage";
import { ROUTES } from "@/router/routes";
import { SplashBackground } from "./splash-background";
import { SplashLogo } from "./splash-logo";
import { SplashContent } from "./splash-content";
import { SplashButton } from "./splash-button";
import styles from "./splash-screen.module.css";
export function SplashScreen() {
const router = useRouter();
const state = useAuthState();
const authDispatch = useAuthDispatch();
const userDispatch = useUserDispatch();
const handleSkip = () => {
authDispatch({ type: "AuthGuestLoginSubmitted" });
router.replace(ROUTES.chat);
};
// ─────────────────────────────────────────────────────────────
// useEffect ① 启动时检查 localStorage 里的 loginToken
// - loginTokenOAuth/email sync 写下的)→ 视为"已登录"跳 /chat
// - 不查 guestToken —— 游客用户停留 splash(按你要求)
// - 都没有 → 留 splash
// proxy 读不到 localStorageEdge runtime),所以这条 fallback 必须在 Client 侧。
// ─────────────────────────────────────────────────────────────
useEffect(() => {
let cancelled = false;
void (async () => {
// 用 getLoginToken() 拿 string | null(不是 hasLoginToken() 拿 boolean
const r = await AuthStorage.getInstance().getLoginToken();
if (!cancelled && r.success && r.data) {
router.replace(ROUTES.chat);
}
})();
return () => {
cancelled = true;
};
}, [router]);
// ─────────────────────────────────────────────────────────────
// useEffect ② 按钮刚派了事件(pendingRedirect=true +
// loginStatus 是非 notLoggedIn → 跳 /chat
//
// 关键:`pendingRedirect` 是 auth context 里的持久状态(不是 ref)——
// re-mount 时不重置,区分"刚按了"和"历史登录"准确无误。
// 跳完调 `AuthClearPendingRedirect` 置 false(一次性信号)。
// ─────────────────────────────────────────────────────────────
useEffect(() => {
// 调试日志(保留,不删 —— 用于排查"re-mount 误跳 /chat"问题)
console.log("[splash] useEffect ② (loginStatus + pendingRedirect)", {
pending: state.pendingRedirect,
loginStatus: state.loginStatus,
willRedirect:
state.pendingRedirect && state.loginStatus !== "notLoggedIn",
});
if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") {
console.log(
"[splash] useEffect ② → router.replace(/chat) [via pendingRedirect flag]",
);
authDispatch({ type: "AuthClearPendingRedirect" });
userDispatch({ type: "UserInit" });
router.replace(ROUTES.chat);
}
}, [
state.loginStatus,
state.pendingRedirect,
authDispatch,
userDispatch,
router,
]);
return (
<MobileShell background="var(--color-sidebar-background)">
<div className={styles.wrapper}>
<SplashBackground />
{/* 渐变叠加层:accent → transparent (bottom-left → center-right) */}
<div className={styles.gradientOverlay} aria-hidden="true" />
<div className={styles.content}>
<SplashLogo />
<div className={styles.spacer} />
<SplashContent />
<div className={styles.buttonArea}>
<SplashButton onSkip={handleSkip} />
</div>
{/*
* 登录错误提示 —— auth state machine 的 onError 会设 errorMessage
* (如 Facebook 同步后端失败 / 后端返回数据 schema 不匹配等)。
* 之前没显示,导致用户卡在 splash 看不到原因。
*/}
{state.errorMessage ? (
<p
role="alert"
style={{
color: "#c0392b",
fontSize: "0.875rem",
marginTop: "1rem",
textAlign: "center",
maxWidth: "320px",
lineHeight: 1.4,
}}
>
{state.errorMessage}
</p>
) : null}
<p className={styles.bottom}>
Elio Silvestri, Your exclusive AI boyfriend
<br />
24/7 online | Chat | Companion | Heal | Sweet moments
</p>
</div>
</div>
</MobileShell>
);
}