refactor(app): move route screens out of components

Exclude test and spec files from generated barrels so page imports do not load test modules at runtime.
This commit is contained in:
2026-06-17 14:13:42 +08:00
parent cda76a1651
commit 287affa34a
16 changed files with 53 additions and 47 deletions
+137
View File
@@ -0,0 +1,137 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core";
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 { pwaUtil } from "@/utils/pwa";
import {
SplashBackground,
SplashButton,
SplashContent,
SplashLogo,
} from "./components";
import styles from "./components/splash-screen.module.css";
// 尽量早地缓存 beforeinstallprompt,避免用户停留 splash 时浏览器事件已触发并丢失。
if (typeof window !== "undefined") {
pwaUtil.prepareInstallPrompt();
}
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(() => {
pwaUtil.prepareInstallPrompt();
}, []);
// ─────────────────────────────────────────────────────────────
// 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>
);
}