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.
This commit is contained in:
2026-06-17 11:51:35 +08:00
parent 015ec111bd
commit c07a10ee80
3 changed files with 15 additions and 10 deletions
+7 -8
View File
@@ -6,16 +6,15 @@ import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { LoadingIndicator } from "@/app/_components/core/loading-indicator"; import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
import styles from "./splash-button.module.css"; import styles from "./splash-button.module.css";
export function SplashButton() { export interface SplashButtonProps {
onSkip: () => void;
}
export function SplashButton({ onSkip }: SplashButtonProps) {
const state = useAuthState(); const state = useAuthState();
const authDispatch = useAuthDispatch(); const authDispatch = useAuthDispatch();
const isLoading = state.isLoading; const isLoading = state.isLoading;
// Skip = 显式游客登录入口("以游客身份继续")
const handleSkip = () => {
authDispatch({ type: "AuthGuestLoginSubmitted" });
};
const handleFacebookLogin = () => { const handleFacebookLogin = () => {
authDispatch({ authDispatch({
type: "AuthFacebookLoginSubmitted", type: "AuthFacebookLoginSubmitted",
@@ -25,10 +24,10 @@ export function SplashButton() {
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
{/* Skip 按钮 = 显式游客登录入口(派发 AuthGuestLoginSubmitted,不做路由跳转 */} {/* Skip 按钮 = 显式游客登录入口(派发事件 + 立即跳 /chat */}
<button <button
type="button" type="button"
onClick={handleSkip} onClick={onSkip}
disabled={isLoading} disabled={isLoading}
className={styles.skip} className={styles.skip}
> >
+6 -1
View File
@@ -21,6 +21,11 @@ export function SplashScreen() {
const authDispatch = useAuthDispatch(); const authDispatch = useAuthDispatch();
const userDispatch = useUserDispatch(); const userDispatch = useUserDispatch();
const handleSkip = () => {
authDispatch({ type: "AuthGuestLoginSubmitted" });
router.replace(ROUTES.chat);
};
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
// useEffect ① 启动时检查 localStorage 里的 loginToken // useEffect ① 启动时检查 localStorage 里的 loginToken
// - loginTokenOAuth/email sync 写下的)→ 视为"已登录"跳 /chat // - loginTokenOAuth/email sync 写下的)→ 视为"已登录"跳 /chat
@@ -86,7 +91,7 @@ export function SplashScreen() {
<div className={styles.spacer} /> <div className={styles.spacer} />
<SplashContent /> <SplashContent />
<div className={styles.buttonArea}> <div className={styles.buttonArea}>
<SplashButton /> <SplashButton onSkip={handleSkip} />
</div> </div>
{/* {/*
* 登录错误提示 —— auth state machine 的 onError 会设 errorMessage * 登录错误提示 —— auth state machine 的 onError 会设 errorMessage
+2 -1
View File
@@ -134,7 +134,8 @@ export const authMachine = setup({
// 内联 assign —— XState v5 type inference 保留 loadingGuestLogin state 类型 // 内联 assign —— XState v5 type inference 保留 loadingGuestLogin state 类型
actions: assign({ actions: assign({
loginStatus: ({ event }) => event.output, loginStatus: ({ event }) => event.output,
pendingRedirect: true, // ← 显式登录成功 → splash / auth 看到跳 /chat // Skip 点击后已在 splash 里立即跳 /chatguest login 不再依赖 pendingRedirect。
pendingRedirect: false,
errorMessage: null, errorMessage: null,
}), }),
}, },